convert kml to Format class (#550)
authortsteven4 <13596209+tsteven4@users.noreply.github.com>
Fri, 1 May 2020 13:03:31 +0000 (07:03 -0600)
committerGitHub <noreply@github.com>
Fri, 1 May 2020 13:03:31 +0000 (07:03 -0600)
* convert kml to Format class.

* update dependencies.

* restore kml real time postion writer functionality.

and have it produce valid kml.

Add a test case.

* correct new kml test reference file name.

CMakeLists.txt
GPSBabel.pro
Makefile.in
kml.cc
kml.h [new file with mode: 0644]
reference/realtime.kml [new file with mode: 0644]
testo.d/kml.test
vecs.h

index eb17c65611338adce2adac40b12c01c55421c487..7e2841e191e0822c593ddd439a80be4ccfd7f59a 100644 (file)
@@ -149,6 +149,7 @@ set(HEADERS
   jeeps/gpsusbcommon.h
   jeeps/gpsusbint.h
   jeeps/gpsutil.h
+  kml.h
   legacyformat.h
   lowranceusr.h
   magellan.h
index a2316cd4b7eb20c032afd55d17295236049fb88a..666ca103cd6b8fdf4ba7d5f01fd65df6f1e4ab89 100644 (file)
@@ -134,6 +134,7 @@ HEADERS =  \
        jeeps/gpsusbcommon.h \
        jeeps/gpsusbint.h \
        jeeps/gpsutil.h \
+       kml.h \
        legacyformat.h \
        lowranceusr.h \
        magellan.h \
index 4d39de46f832150eea8316df79974038b23a85d2..17237f7a4a2f77b2d2685853de81fe9bc0eb1d2e 100644 (file)
@@ -510,12 +510,13 @@ filter_vecs.o: filter_vecs.cc defs.h config.h zlib/zlib.h zlib/zconf.h \
   swapdata.h trackfilter.h transform.h validate.h gbversion.h vecs.h \
   format.h energympro.h garmin_fit.h geojson.h src/core/file.h ggv_bin.h \
   globalsat_sport.h gpx.h src/core/xmlstreamwriter.h src/core/xmltag.h \
-  legacyformat.h lowranceusr.h mynav.h qstarz_bl_1000.h nmea.h random.h \
-  shape.h shapelib/shapefil.h subrip.h xcsv.h garmin_fs.h jeeps/gps.h \
-  jeeps/../defs.h jeeps/gpsport.h jeeps/gpsdevice.h jeeps/gpssend.h \
-  jeeps/gpsread.h jeeps/gpsutil.h jeeps/gpsapp.h jeeps/gpsprot.h \
-  jeeps/gpscom.h jeeps/gpsfmt.h jeeps/gpsmath.h jeeps/gpsmem.h \
-  jeeps/gpsrqst.h src/core/textstream.h yahoo.h xmlgeneric.h
+  kml.h xmlgeneric.h legacyformat.h lowranceusr.h mynav.h nmea.h \
+  qstarz_bl_1000.h random.h shape.h shapelib/shapefil.h subrip.h xcsv.h \
+  garmin_fs.h jeeps/gps.h jeeps/../defs.h jeeps/gpsport.h \
+  jeeps/gpsdevice.h jeeps/gpssend.h jeeps/gpsread.h jeeps/gpsutil.h \
+  jeeps/gpsapp.h jeeps/gpsprot.h jeeps/gpscom.h jeeps/gpsfmt.h \
+  jeeps/gpsmath.h jeeps/gpsmem.h jeeps/gpsrqst.h src/core/textstream.h \
+  yahoo.h
 formspec.o: formspec.cc defs.h config.h zlib/zlib.h zlib/zconf.h \
   formspec.h inifile.h gbfile.h session.h src/core/datetime.h \
   src/core/optional.h
@@ -535,9 +536,9 @@ garmin.o: garmin.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \
   jeeps/gpsrqst.h garmin_tables.h grtcirc.h jeeps/gpsserial.h vecs.h \
   energympro.h garmin_fit.h geojson.h src/core/file.h ggv_bin.h \
   globalsat_sport.h gpx.h src/core/xmlstreamwriter.h src/core/xmltag.h \
-  legacyformat.h lowranceusr.h mynav.h qstarz_bl_1000.h nmea.h random.h \
-  shape.h shapelib/shapefil.h subrip.h xcsv.h src/core/textstream.h \
-  yahoo.h xmlgeneric.h
+  kml.h xmlgeneric.h legacyformat.h lowranceusr.h mynav.h nmea.h \
+  qstarz_bl_1000.h random.h shape.h shapelib/shapefil.h subrip.h xcsv.h \
+  src/core/textstream.h yahoo.h
 garmin_device_xml.o: garmin_device_xml.cc defs.h config.h zlib/zlib.h \
   zlib/zconf.h formspec.h inifile.h gbfile.h session.h \
   src/core/datetime.h src/core/optional.h garmin_device_xml.h \
@@ -806,8 +807,8 @@ jtr.o: jtr.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \
   csv_util.h nmea.h format.h
 kml.o: kml.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \
   inifile.h gbfile.h session.h src/core/datetime.h src/core/optional.h \
-  grtcirc.h src/core/file.h src/core/logging.h \
-  src/core/xmlstreamwriter.h src/core/xmltag.h units.h xmlgeneric.h
+  kml.h format.h src/core/file.h src/core/xmlstreamwriter.h xmlgeneric.h \
+  grtcirc.h src/core/logging.h src/core/xmltag.h units.h
 lmx.o: lmx.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \
   inifile.h gbfile.h session.h src/core/datetime.h src/core/optional.h \
   xmlgeneric.h
@@ -822,12 +823,13 @@ magproto.o: magproto.cc defs.h config.h zlib/zlib.h zlib/zconf.h \
   src/core/optional.h explorist_ini.h format.h gbser.h magellan.h vecs.h \
   energympro.h garmin_fit.h geojson.h src/core/file.h ggv_bin.h \
   globalsat_sport.h gpx.h src/core/xmlstreamwriter.h src/core/xmltag.h \
-  legacyformat.h lowranceusr.h mynav.h qstarz_bl_1000.h nmea.h random.h \
-  shape.h shapelib/shapefil.h subrip.h xcsv.h garmin_fs.h jeeps/gps.h \
-  jeeps/../defs.h jeeps/gpsport.h jeeps/gpsdevice.h jeeps/gpssend.h \
-  jeeps/gpsread.h jeeps/gpsutil.h jeeps/gpsapp.h jeeps/gpsprot.h \
-  jeeps/gpscom.h jeeps/gpsfmt.h jeeps/gpsmath.h jeeps/gpsmem.h \
-  jeeps/gpsrqst.h src/core/textstream.h yahoo.h xmlgeneric.h
+  kml.h xmlgeneric.h legacyformat.h lowranceusr.h mynav.h nmea.h \
+  qstarz_bl_1000.h random.h shape.h shapelib/shapefil.h subrip.h xcsv.h \
+  garmin_fs.h jeeps/gps.h jeeps/../defs.h jeeps/gpsport.h \
+  jeeps/gpsdevice.h jeeps/gpssend.h jeeps/gpsread.h jeeps/gpsutil.h \
+  jeeps/gpsapp.h jeeps/gpsprot.h jeeps/gpscom.h jeeps/gpsfmt.h \
+  jeeps/gpsmath.h jeeps/gpsmem.h jeeps/gpsrqst.h src/core/textstream.h \
+  yahoo.h
 main.o: main.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \
   inifile.h gbfile.h session.h src/core/datetime.h src/core/optional.h \
   cet_util.h csv_util.h filter.h filter_vecs.h arcdist.h bend.h \
@@ -836,13 +838,13 @@ main.o: main.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \
   stackfilter.h swapdata.h trackfilter.h transform.h validate.h format.h \
   src/core/file.h src/core/usasciicodec.h vecs.h energympro.h \
   garmin_fit.h geojson.h ggv_bin.h globalsat_sport.h gpx.h \
-  src/core/xmlstreamwriter.h src/core/xmltag.h legacyformat.h \
-  lowranceusr.h mynav.h qstarz_bl_1000.h nmea.h random.h shape.h \
-  shapelib/shapefil.h subrip.h xcsv.h garmin_fs.h jeeps/gps.h \
+  src/core/xmlstreamwriter.h src/core/xmltag.h kml.h xmlgeneric.h \
+  legacyformat.h lowranceusr.h mynav.h nmea.h qstarz_bl_1000.h random.h \
+  shape.h shapelib/shapefil.h subrip.h xcsv.h garmin_fs.h jeeps/gps.h \
   jeeps/../defs.h jeeps/gpsport.h jeeps/gpsdevice.h jeeps/gpssend.h \
   jeeps/gpsread.h jeeps/gpsutil.h jeeps/gpsapp.h jeeps/gpsprot.h \
   jeeps/gpscom.h jeeps/gpsfmt.h jeeps/gpsmath.h jeeps/gpsmem.h \
-  jeeps/gpsrqst.h src/core/textstream.h yahoo.h xmlgeneric.h
+  jeeps/gpsrqst.h src/core/textstream.h yahoo.h
 mapasia.o: mapasia.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \
   inifile.h gbfile.h session.h src/core/datetime.h src/core/optional.h
 mapbar_track.o: mapbar_track.cc defs.h config.h zlib/zlib.h zlib/zconf.h \
@@ -996,7 +998,7 @@ stmsdf.o: stmsdf.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \
   src/core/logging.h
 stmwpp.o: stmwpp.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \
   inifile.h gbfile.h session.h src/core/datetime.h src/core/optional.h \
-  csv_util.h cet_util.h
+  cet_util.h csv_util.h
 strptime.o: strptime.c config.h strptime.h
 subrip.o: subrip.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \
   inifile.h gbfile.h session.h src/core/datetime.h src/core/optional.h \
@@ -1059,13 +1061,13 @@ vecs.o: vecs.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \
   inifile.h gbfile.h session.h src/core/datetime.h src/core/optional.h \
   vecs.h format.h energympro.h garmin_fit.h geojson.h src/core/file.h \
   ggv_bin.h globalsat_sport.h gpx.h src/core/xmlstreamwriter.h \
-  src/core/xmltag.h legacyformat.h lowranceusr.h mynav.h \
-  qstarz_bl_1000.h nmea.h random.h shape.h shapelib/shapefil.h subrip.h \
-  xcsv.h garmin_fs.h jeeps/gps.h jeeps/../defs.h jeeps/gpsport.h \
-  jeeps/gpsdevice.h jeeps/gpssend.h jeeps/gpsread.h jeeps/gpsutil.h \
-  jeeps/gpsapp.h jeeps/gpsprot.h jeeps/gpscom.h jeeps/gpsfmt.h \
-  jeeps/gpsmath.h jeeps/gpsmem.h jeeps/gpsrqst.h src/core/textstream.h \
-  yahoo.h xmlgeneric.h gbversion.h src/core/logging.h
+  src/core/xmltag.h kml.h xmlgeneric.h legacyformat.h lowranceusr.h \
+  mynav.h nmea.h qstarz_bl_1000.h random.h shape.h shapelib/shapefil.h \
+  subrip.h xcsv.h garmin_fs.h jeeps/gps.h jeeps/../defs.h \
+  jeeps/gpsport.h jeeps/gpsdevice.h jeeps/gpssend.h jeeps/gpsread.h \
+  jeeps/gpsutil.h jeeps/gpsapp.h jeeps/gpsprot.h jeeps/gpscom.h \
+  jeeps/gpsfmt.h jeeps/gpsmath.h jeeps/gpsmem.h jeeps/gpsrqst.h \
+  src/core/textstream.h yahoo.h gbversion.h src/core/logging.h
 vidaone.o: vidaone.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \
   inifile.h gbfile.h session.h src/core/datetime.h src/core/optional.h
 vitosmt.o: vitosmt.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \
diff --git a/kml.cc b/kml.cc
index dfaaef939cfc0a8fdb524a763c9b329b8a127e2f..83c0d18e9c94a3a51673d99915370a95aa8f86f1 100644 (file)
--- a/kml.cc
+++ b/kml.cc
@@ -47,6 +47,7 @@
 #include <QtCore/QtGlobal>              // for foreach, qint64, qPrintable
 
 #include "defs.h"
+#include "kml.h"
 #include "formspec.h"                   // for FsChainFind, kFsGpx
 #include "grtcirc.h"                    // for RAD, gcdist, radtometers
 #include "src/core/datetime.h"          // for DateTime
 #include "xmlgeneric.h"                 // for cb_cdata, cb_end, cb_start, xg_callback, xg_string, xg_cb_type, xml_deinit, xml_ignore_tags, xml_init, xml_read, xg_tag_mapping
 
 
-// options
-static char* opt_deficon = nullptr;
-static char* opt_export_lines = nullptr;
-static char* opt_export_points = nullptr;
-static char* opt_export_track = nullptr;
-static char* opt_line_width = nullptr;
-static char* opt_line_color = nullptr;
-static char* opt_floating = nullptr;
-static char* opt_extrude = nullptr;
-static char* opt_trackdata = nullptr;
-static char* opt_trackdirection = nullptr;
-static char* opt_units = nullptr;
-static char* opt_labels = nullptr;
-static char* opt_max_position_points = nullptr;
-static char* opt_rotate_colors = nullptr;
-static char* opt_precision = nullptr;
-
-static int export_lines;
-static int export_points;
-static int export_track;
-static int floating;
-static int extrude;
-static int trackdata;
-static int trackdirection;
-static int max_position_points;
-static int rotate_colors;
-static int line_width;
-static int html_encrypt;
-static int precision;
-
-static Waypoint* wpt_tmp;
-static int wpt_tmp_queued;
-static QString posnfilename;
-static QString posnfilenametmp;
-
-static route_head* gx_trk_head;
-static QList<gpsbabel::DateTime>* gx_trk_times;
-static QList<std::tuple<int, double, double, double>>* gx_trk_coords;
-
-static gpsbabel::File* oqfile;
-static gpsbabel::XmlStreamWriter* writer;
-
-enum kml_point_type {
-  kmlpt_unknown,
-  kmlpt_waypoint,
-  kmlpt_track,
-  kmlpt_route,
-  kmlpt_multitrack,
-  kmlpt_other
-};
-
-static int realtime_positioning;
-static bounds kml_bounds;
-static gpsbabel::DateTime kml_time_max;
-static gpsbabel::DateTime kml_time_min;
-
-#define DEFAULT_PRECISION "6"
-
 //  Icons provided and hosted by Google.  Used with permission.
 #define ICON_BASE "https://earth.google.com/images/kml-icons/"
-
-// Multitrack ids to correlate Schema to SchemaData
-static const char kmt_heartrate[] = "heartrate";
-static const char kmt_cadence[] = "cadence";
-static const char kmt_temperature[] = "temperature";
-static const char kmt_depth[] = "depth";
-static const char kmt_power[] = "power";
-
-
-static
-QVector<arglist_t> kml_args = {
-  {"deficon", &opt_deficon, "Default icon name", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr },
-  {
-    "lines", &opt_export_lines,
-    "Export linestrings for tracks and routes",
-    "1", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr,
-  },
-  {
-    "points", &opt_export_points,
-    "Export placemarks for tracks and routes",
-    "1", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
-  },
-  {
-    "line_width", &opt_line_width,
-    "Width of lines, in pixels",
-    "6", ARGTYPE_INT, ARG_NOMINMAX, nullptr
-  },
-  {
-    "line_color", &opt_line_color,
-    "Line color, specified in hex AABBGGRR",
-    "99ffac59", ARGTYPE_STRING, ARG_NOMINMAX, nullptr
-  },
-  {
-    "floating", &opt_floating,
-    "Altitudes are absolute and not clamped to ground",
-    "0", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
-  },
-  {
-    "extrude", &opt_extrude,
-    "Draw extrusion line from trackpoint to ground",
-    "0", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
-  },
-  {
-    "track", &opt_export_track,
-    "Write KML track (default = 0)",
-    "0", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
-  },
-  {
-    "trackdata", &opt_trackdata,
-    "Include extended data for trackpoints (default = 1)",
-    "1", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
-  },
-  {
-    "trackdirection", &opt_trackdirection,
-    "Indicate direction of travel in track icons (default = 0)",
-    "0", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
-  },
-  {
-    "units", &opt_units,
-    "Units used when writing comments ('s'tatute, 'm'etric,' 'n'autical, 'a'viation)",
-    "s", ARGTYPE_STRING, ARG_NOMINMAX, nullptr
-  },
-  {
-    "labels", &opt_labels,
-    "Display labels on track and routepoints  (default = 1)",
-    "1", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
-  },
-  {
-    "max_position_points", &opt_max_position_points,
-    "Retain at most this number of position points  (0 = unlimited)",
-    "0", ARGTYPE_INT, ARG_NOMINMAX, nullptr
-  },
-  {
-    "rotate_colors", &opt_rotate_colors,
-    "Rotate colors for tracks and routes (default automatic)",
-    nullptr, ARGTYPE_FLOAT, "0", "360", nullptr
-  },
-  {
-    "prec", &opt_precision,
-    "Precision of coordinates, number of decimals",
-    DEFAULT_PRECISION, ARGTYPE_INT, ARG_NOMINMAX, nullptr
-  },
-};
-
-static
-struct {
-  int freshness;
-  const char* icon;
-} kml_tracking_icons[] = {
-  { 60, ICON_BASE "youarehere-60.png" }, // Red
-  { 30, ICON_BASE "youarehere-30.png" }, // Yellow
-  { 0,  ICON_BASE "youarehere-0.png" }, // Green
-};
-
 #define ICON_NOSAT ICON_BASE "youarehere-warning.png"
 #define ICON_WPT "https://maps.google.com/mapfiles/kml/pal4/icon61.png"
 #define ICON_TRK ICON_BASE "track-directional/track-none.png"
@@ -220,51 +69,44 @@ struct {
 #define ICON_MULTI_TRK ICON_BASE "track-directional/track-0.png"
 #define ICON_DIR ICON_BASE "track-directional/track-%1.png" // format string where next arg is rotational degrees.
 
-static struct {
-  float seq{0.0f};
-  float step{0.0f};
-  gb_color color;
-} kml_color_sequencer;
-#define KML_COLOR_LIMIT 204    /* allowed range [0,255] */
-
 #define MYNAME "kml"
 
-static void kml_init_color_sequencer(unsigned int steps_per_rev)
+void KmlFormat::kml_init_color_sequencer(unsigned int steps_per_rev)
 {
   if (rotate_colors) {
     float color_step = atof(opt_rotate_colors);
     if (color_step > 0.0f) {
       // step around circle by given number of degrees for each track(route)
-      kml_color_sequencer.step = ((float)KML_COLOR_LIMIT) * 6.0f * color_step / 360.0f;
+      kml_color_sequencer.step = ((float)kml_color_limit) * 6.0f * color_step / 360.0f;
     } else {
       // one cycle around circle for all the tracks(routes)
-      kml_color_sequencer.step = ((float)KML_COLOR_LIMIT) * 6.0f / ((float)steps_per_rev);
+      kml_color_sequencer.step = ((float)kml_color_limit) * 6.0f / ((float)steps_per_rev);
     }
     kml_color_sequencer.color.opacity=255;
     kml_color_sequencer.seq = 0.0f;
   }
 }
 
-static void kml_step_color()
+void KmlFormat::kml_step_color()
 {
-  // Map kml_color_sequencer.seq to an integer in the range [0, KML_COLOR_LIMIT*6).
+  // Map kml_color_sequencer.seq to an integer in the range [0, kml_color_limit*6).
   // Note that color_seq may be outside this range if the cast from float to int fails.
-  int color_seq = ((int) kml_color_sequencer.seq) % (KML_COLOR_LIMIT * 6);
+  int color_seq = ((int) kml_color_sequencer.seq) % (kml_color_limit * 6);
   if (global_opts.debug_level >= 1) {
     printf(MYNAME ": kml_color_sequencer seq %f %d, step %f\n",kml_color_sequencer.seq, color_seq, kml_color_sequencer.step);
   }
-  if ((color_seq >= (0*KML_COLOR_LIMIT)) && (color_seq < (1*KML_COLOR_LIMIT))) {
-    kml_color_sequencer.color.bbggrr = (0)<<16 | (color_seq)<<8 | (KML_COLOR_LIMIT);
-  } else if ((color_seq >= (1*KML_COLOR_LIMIT)) && (color_seq < (2*KML_COLOR_LIMIT))) {
-    kml_color_sequencer.color.bbggrr = (0)<<16 | (KML_COLOR_LIMIT)<<8 | (2*KML_COLOR_LIMIT-color_seq);
-  } else if ((color_seq >= (2*KML_COLOR_LIMIT)) && (color_seq < (3*KML_COLOR_LIMIT))) {
-    kml_color_sequencer.color.bbggrr = (color_seq-2*KML_COLOR_LIMIT)<<16 | (KML_COLOR_LIMIT)<<8 | (0);
-  } else if ((color_seq >= (3*KML_COLOR_LIMIT)) && (color_seq < (4*KML_COLOR_LIMIT))) {
-    kml_color_sequencer.color.bbggrr = (KML_COLOR_LIMIT)<<16 | (4*KML_COLOR_LIMIT-color_seq)<<8 | (0);
-  } else if ((color_seq >= (4*KML_COLOR_LIMIT)) && (color_seq < (5*KML_COLOR_LIMIT))) {
-    kml_color_sequencer.color.bbggrr = (KML_COLOR_LIMIT)<<16 | (0)<<8 | (color_seq-4*KML_COLOR_LIMIT);
-  } else if ((color_seq >= (5*KML_COLOR_LIMIT)) && (color_seq < (6*KML_COLOR_LIMIT))) {
-    kml_color_sequencer.color.bbggrr = (6*KML_COLOR_LIMIT-color_seq)<<16 | (0)<<8 | (KML_COLOR_LIMIT);
+  if ((color_seq >= (0*kml_color_limit)) && (color_seq < (1*kml_color_limit))) {
+    kml_color_sequencer.color.bbggrr = (0)<<16 | (color_seq)<<8 | (kml_color_limit);
+  } else if ((color_seq >= (1*kml_color_limit)) && (color_seq < (2*kml_color_limit))) {
+    kml_color_sequencer.color.bbggrr = (0)<<16 | (kml_color_limit)<<8 | (2*kml_color_limit-color_seq);
+  } else if ((color_seq >= (2*kml_color_limit)) && (color_seq < (3*kml_color_limit))) {
+    kml_color_sequencer.color.bbggrr = (color_seq-2*kml_color_limit)<<16 | (kml_color_limit)<<8 | (0);
+  } else if ((color_seq >= (3*kml_color_limit)) && (color_seq < (4*kml_color_limit))) {
+    kml_color_sequencer.color.bbggrr = (kml_color_limit)<<16 | (4*kml_color_limit-color_seq)<<8 | (0);
+  } else if ((color_seq >= (4*kml_color_limit)) && (color_seq < (5*kml_color_limit))) {
+    kml_color_sequencer.color.bbggrr = (kml_color_limit)<<16 | (0)<<8 | (color_seq-4*kml_color_limit);
+  } else if ((color_seq >= (5*kml_color_limit)) && (color_seq < (6*kml_color_limit))) {
+    kml_color_sequencer.color.bbggrr = (6*kml_color_limit-color_seq)<<16 | (0)<<8 | (kml_color_limit);
   } else { // should not occur, but to be safe generate a legal color.
     warning(MYNAME ": Error in color conversion - using default color.\n");
     kml_color_sequencer.color.bbggrr = (102)<<16 | (102)<<8 | (102);
@@ -273,53 +115,14 @@ static void kml_step_color()
   kml_color_sequencer.seq = kml_color_sequencer.seq + kml_color_sequencer.step;
 }
 
-static xg_callback wpt_s, wpt_e;
-static xg_callback wpt_name, wpt_desc, wpt_coord, wpt_icon, trk_coord, wpt_time, wpt_ts_begin, wpt_ts_end;
-static xg_callback gx_trk_s, gx_trk_e;
-static xg_callback gx_trk_when, gx_trk_coord;
-
-static
-xg_tag_mapping kml_map[] = {
-  { wpt_s,     cb_start,       "/Placemark" },
-  { wpt_e,     cb_end,         "/Placemark" },
-  { wpt_name,  cb_cdata,       "/Placemark/name" },
-  { wpt_desc,  cb_cdata,       "/Placemark/description" },
-  { wpt_ts_begin, cb_cdata,    "/Placemark/TimeSpan/begin" },
-  { wpt_ts_end, cb_cdata,      "/Placemark/TimeSpan/end" },
-  { wpt_time,  cb_cdata,       "/Placemark/TimeStamp/when" },
-  // Alias for above used in KML 2.0
-  { wpt_time,  cb_cdata,       "/Placemark/TimeInstant/timePosition" },
-  { wpt_coord,         cb_cdata,       "/Placemark/Point/coordinates" },
-  { wpt_icon,  cb_cdata,       "/Placemark/Style/Icon/href" },
-  { trk_coord,         cb_cdata,       "/Placemark/MultiGeometry/LineString/coordinates" },
-  { trk_coord,         cb_cdata,       "/Placemark/GeometryCollection/LineString/coordinates" },
-  { trk_coord,         cb_cdata,       "/Placemark/Polygon/outerBoundaryIs/LinearRing/coordinates" },
-  { trk_coord,         cb_cdata,       "/Placemark/LineString/coordinates" },
-  { gx_trk_s,          cb_start,       "/Placemark/*gx:Track" },
-  { gx_trk_e,          cb_end,         "/Placemark/*gx:Track" },
-  { gx_trk_when,  cb_cdata, "/Placemark/*gx:Track/when" },
-  { gx_trk_coord, cb_cdata, "/Placemark/*gx:Track/gx:coord" },
-  { gx_trk_s,          cb_start,       "/Placemark/Track" }, // KML 2.3
-  { gx_trk_e,          cb_end,         "/Placemark/Track" }, // KML 2.3
-  { gx_trk_when,  cb_cdata, "/Placemark/Track/when" }, // KML 2.3
-  { gx_trk_coord, cb_cdata, "/Placemark/Track/coord" }, // KML 2.3
-  { gx_trk_s,          cb_start,       "/Placemark/MultiTrack/Track" }, // KML 2.3
-  { gx_trk_e,          cb_end,         "/Placemark/MultiTrack/Track" }, // KML 2.3
-  { gx_trk_when,  cb_cdata, "/Placemark/MultiTrack/Track/when" }, // KML 2.3
-  { gx_trk_coord, cb_cdata, "/Placemark/MultiTrack/Track/coord" }, // KML 2.3
-  { nullptr,   (xg_cb_type) 0,                 nullptr }
-};
-
-static
-const char* kml_tags_to_ignore[] = {
+const char* KmlFormat::kml_tags_to_ignore[] = {
   "kml",
   "Document",
   "Folder",
   nullptr
 };
 
-static
-const char* kml_tags_to_skip[] = {
+const char* KmlFormat::kml_tags_to_skip[] = {
   "Camera",
   "LookAt",
   "styleUrl",
@@ -327,16 +130,13 @@ const char* kml_tags_to_skip[] = {
   nullptr
 };
 
-// The TimeSpan/begin and TimeSpan/end DateTimes:
-static gpsbabel::DateTime wpt_timespan_begin, wpt_timespan_end;
-
-void wpt_s(xg_string, const QXmlStreamAttributes*)
+void KmlFormat::wpt_s(xg_string /*args*/, const QXmlStreamAttributes* /*attrs*/)
 {
   if (wpt_tmp) {
     fatal(MYNAME ": wpt_s: invalid kml file\n");
   }
   wpt_tmp = new Waypoint;
-  wpt_tmp_queued = 0;
+  wpt_tmp_queued = false;
 
   /* Invalidate timespan elements for a beginning Placemark,
    * so that each Placemark has its own (or no) TimeSpan. */
@@ -344,7 +144,7 @@ void wpt_s(xg_string, const QXmlStreamAttributes*)
   wpt_timespan_end = gpsbabel::DateTime();
 }
 
-void wpt_e(xg_string, const QXmlStreamAttributes*)
+void KmlFormat::wpt_e(xg_string /*args*/, const QXmlStreamAttributes* /*attrs*/)
 {
   if (!wpt_tmp) {
     fatal(MYNAME ": wpt_e: invalid kml file\n");
@@ -356,10 +156,10 @@ void wpt_e(xg_string, const QXmlStreamAttributes*)
     delete wpt_tmp;
     wpt_tmp = nullptr;
   }
-  wpt_tmp_queued = 0;
+  wpt_tmp_queued = false;
 }
 
-void wpt_name(xg_string args, const QXmlStreamAttributes*)
+void KmlFormat::wpt_name(xg_string args, const QXmlStreamAttributes* /*attrs*/)
 {
   if (!wpt_tmp) {
     fatal(MYNAME ": wpt_name: invalid kml file\n");
@@ -367,7 +167,7 @@ void wpt_name(xg_string args, const QXmlStreamAttributes*)
   wpt_tmp->shortname = args;
 }
 
-void wpt_desc(const QString& args, const QXmlStreamAttributes*)
+void KmlFormat::wpt_desc(const QString& args, const QXmlStreamAttributes* /*attrs*/)
 {
   if (!wpt_tmp) {
     fatal(MYNAME ": wpt_desc: invalid kml file\n");
@@ -375,7 +175,7 @@ void wpt_desc(const QString& args, const QXmlStreamAttributes*)
   wpt_tmp->description += args.trimmed();
 }
 
-void wpt_time(xg_string args, const QXmlStreamAttributes*)
+void KmlFormat::wpt_time(xg_string args, const QXmlStreamAttributes* /*attrs*/)
 {
   if (!wpt_tmp) {
     fatal(MYNAME ": wpt_time: invalid kml file\n");
@@ -383,17 +183,17 @@ void wpt_time(xg_string args, const QXmlStreamAttributes*)
   wpt_tmp->SetCreationTime(xml_parse_time(args));
 }
 
-void wpt_ts_begin(xg_string args, const QXmlStreamAttributes*)
+void KmlFormat::wpt_ts_begin(xg_string args, const QXmlStreamAttributes* /*attrs*/)
 {
   wpt_timespan_begin = xml_parse_time(args);
 }
 
-void wpt_ts_end(xg_string args, const QXmlStreamAttributes*)
+void KmlFormat::wpt_ts_end(xg_string args, const QXmlStreamAttributes* /*attrs*/)
 {
   wpt_timespan_end = xml_parse_time(args);
 }
 
-void wpt_coord(const QString& args, const QXmlStreamAttributes*)
+void KmlFormat::wpt_coord(const QString& args, const QXmlStreamAttributes* /*attrs*/)
 {
   double lat, lon, alt;
   if (! wpt_tmp) {
@@ -408,17 +208,17 @@ void wpt_coord(const QString& args, const QXmlStreamAttributes*)
   if (n == 3) {
     wpt_tmp->altitude = alt;
   }
-  wpt_tmp_queued = 1;
+  wpt_tmp_queued = true;
 }
 
-void wpt_icon(xg_string args, const QXmlStreamAttributes*)
+void KmlFormat::wpt_icon(xg_string args, const QXmlStreamAttributes* /*attrs*/)
 {
   if (wpt_tmp)  {
     wpt_tmp->icon_descr = args;
   }
 }
 
-void trk_coord(xg_string args, const QXmlStreamAttributes*)
+void KmlFormat::trk_coord(xg_string args, const QXmlStreamAttributes* /*attrs*/)
 {
   auto* trk_head = new route_head;
   if (wpt_tmp && !wpt_tmp->shortname.isEmpty()) {
@@ -427,10 +227,10 @@ void trk_coord(xg_string args, const QXmlStreamAttributes*)
   track_add_head(trk_head);
 
   const auto vecs = args.simplified().split(' ');
-  for(const auto& vec : vecs) {
+  for (const auto& vec : vecs) {
     const QStringList coords = vec.split(',');
     auto csize = coords.size();
-    auto trkpt = new Waypoint;
+    auto* trkpt = new Waypoint;
 
     if (csize == 3) {
       trkpt->altitude = coords[2].toDouble();
@@ -470,7 +270,7 @@ void trk_coord(xg_string args, const QXmlStreamAttributes*)
   }
 }
 
-void gx_trk_s(xg_string, const QXmlStreamAttributes*)
+void KmlFormat::gx_trk_s(xg_string /*args*/, const QXmlStreamAttributes* /*attrs*/)
 {
   gx_trk_head = new route_head;
   if (wpt_tmp && !wpt_tmp->shortname.isEmpty()) {
@@ -486,7 +286,7 @@ void gx_trk_s(xg_string, const QXmlStreamAttributes*)
   gx_trk_coords = new QList<std::tuple<int, double, double, double>>;
 }
 
-void gx_trk_e(xg_string, const QXmlStreamAttributes*)
+void KmlFormat::gx_trk_e(xg_string /*args*/, const QXmlStreamAttributes* /*attrs*/)
 {
   // Check that for every temporal value (kml:when) in a kml:Track there is a position (kml:coord) value.
   // Check that for every temporal value (kml:when) in a gx:Track there is a position (gx:coord) value.
@@ -529,7 +329,7 @@ void gx_trk_e(xg_string, const QXmlStreamAttributes*)
   gx_trk_coords = nullptr;
 }
 
-void gx_trk_when(xg_string args, const QXmlStreamAttributes*)
+void KmlFormat::gx_trk_when(xg_string args, const QXmlStreamAttributes* /*attrs*/)
 {
   if (! gx_trk_times) {
     fatal(MYNAME ": gx_trk_when: invalid kml file\n");
@@ -537,7 +337,7 @@ void gx_trk_when(xg_string args, const QXmlStreamAttributes*)
   gx_trk_times->append(xml_parse_time(args));
 }
 
-void gx_trk_coord(xg_string args, const QXmlStreamAttributes*)
+void KmlFormat::gx_trk_coord(xg_string args, const QXmlStreamAttributes* /*attrs*/)
 {
   if (! gx_trk_coords) {
     fatal(MYNAME ": gx_trk_coord: invalid kml file\n");
@@ -551,28 +351,22 @@ void gx_trk_coord(xg_string args, const QXmlStreamAttributes*)
   gx_trk_coords->append(std::make_tuple(n, lat, lon, alt));
 }
 
-static
-void
-kml_rd_init(const QString& fname)
+void KmlFormat::rd_init(const QString& fname)
 {
-  xml_init(fname, kml_map, nullptr, kml_tags_to_ignore, kml_tags_to_skip);
+  xml_init(fname, build_xg_tag_map(this, kml_map), nullptr, kml_tags_to_ignore, kml_tags_to_skip, true);
 }
 
-static
-void
-kml_read()
+void KmlFormat::read()
 {
   xml_read();
 }
 
-static void
-kml_rd_deinit()
+void KmlFormat::rd_deinit()
 {
   xml_deinit();
 }
 
-static void
-kml_wr_init(const QString& fname)
+void KmlFormat::wr_init(const QString& fname)
 {
   char u = 's';
   waypt_init_bounds(&kml_bounds);
@@ -614,8 +408,7 @@ kml_wr_init(const QString& fname)
  * The magic here is to try to ensure that posnfilename is atomically
  * updated.
  */
-static void
-kml_wr_position_init(const QString& fname)
+void KmlFormat::wr_position_init(const QString& fname)
 {
   posnfilename = fname;
   posnfilenametmp = QString("%1-").arg(fname);
@@ -623,8 +416,7 @@ kml_wr_position_init(const QString& fname)
   max_position_points = atoi(opt_max_position_points);
 }
 
-static void
-kml_wr_deinit()
+void KmlFormat::wr_deinit()
 {
   writer->writeEndDocument();
   delete writer;
@@ -642,8 +434,7 @@ kml_wr_deinit()
   }
 }
 
-static void
-kml_wr_position_deinit()
+void KmlFormat::wr_position_deinit()
 {
 //     kml_wr_deinit();
   posnfilename.clear();
@@ -651,8 +442,7 @@ kml_wr_position_deinit()
 }
 
 
-static void
-kml_output_linestyle(char* /*color*/, int width)
+void KmlFormat::kml_output_linestyle(char* /*color*/, int width) const
 {
   // Style settings for line strings
   writer->writeStartElement(QStringLiteral("LineStyle"));
@@ -662,8 +452,8 @@ kml_output_linestyle(char* /*color*/, int width)
 }
 
 
-static void kml_write_bitmap_style_(const QString& style, const QString& bitmap,
-                                    int highlighted, int force_heading)
+void KmlFormat::kml_write_bitmap_style_(const QString& style, const QString& bitmap,
+                                        int highlighted, int force_heading) const
 {
   int is_track = style.startsWith("track");
   int is_multitrack = style.startsWith("multiTrack");
@@ -708,8 +498,8 @@ static void kml_write_bitmap_style_(const QString& style, const QString& bitmap,
  * and non-highlighted version of the style to allow the icons
  * to magnify slightly on a rollover.
  */
-static void kml_write_bitmap_style(kml_point_type pt_type, const QString& bitmap,
-                                   const QString& customstyle)
+void KmlFormat::kml_write_bitmap_style(kml_point_type pt_type, const QString& bitmap,
+                                       const QString& customstyle) const
 {
   int force_heading = 0;
   QString style;
@@ -751,7 +541,7 @@ static void kml_write_bitmap_style(kml_point_type pt_type, const QString& bitmap
   writer->writeEndElement(); // Close StyleMap tag
 }
 
-static void kml_output_timestamp(const Waypoint* waypointp)
+void KmlFormat::kml_output_timestamp(const Waypoint* waypointp) const
 {
   QString time_string = waypointp->CreationTimeXML();
   if (!time_string.isEmpty()) {
@@ -761,8 +551,7 @@ static void kml_output_timestamp(const Waypoint* waypointp)
   }
 }
 
-static
-void kml_td(gpsbabel::XmlStreamWriter& hwriter, const QString& boldData, const QString& data)
+void KmlFormat::kml_td(gpsbabel::XmlStreamWriter& hwriter, const QString& boldData, const QString& data)
 {
   hwriter.writeCharacters(QStringLiteral("\n"));
   hwriter.writeStartElement(QStringLiteral("tr"));
@@ -773,8 +562,7 @@ void kml_td(gpsbabel::XmlStreamWriter& hwriter, const QString& boldData, const Q
   hwriter.writeEndElement(); // Close tr tag
 }
 
-static
-void kml_td(gpsbabel::XmlStreamWriter& hwriter, const QString& data)
+void KmlFormat::kml_td(gpsbabel::XmlStreamWriter& hwriter, const QString& data)
 {
   hwriter.writeCharacters(QStringLiteral("\n"));
   hwriter.writeStartElement(QStringLiteral("tr"));
@@ -787,8 +575,7 @@ void kml_td(gpsbabel::XmlStreamWriter& hwriter, const QString& data)
 /*
  * Output the track summary.
  */
-static
-void kml_output_trkdescription(const route_head* header, const computed_trkdata* td)
+void KmlFormat::kml_output_trkdescription(const route_head* header, const computed_trkdata* td) const
 {
   if (!td || !trackdata) {
     return;
@@ -874,12 +661,9 @@ void kml_output_trkdescription(const route_head* header, const computed_trkdata*
 }
 
 
-static
-void kml_output_header(const route_head* header, const computed_trkdata* td)
+void KmlFormat::kml_output_header(const route_head* header, const computed_trkdata* td) const
 {
-  if (!realtime_positioning)  {
-    writer->writeStartElement(QStringLiteral("Folder"));
-  }
+  writer->writeStartElement(QStringLiteral("Folder"));
   writer->writeOptionalTextElement(QStringLiteral("name"), header->rte_name);
   kml_output_trkdescription(header, td);
 
@@ -890,8 +674,7 @@ void kml_output_header(const route_head* header, const computed_trkdata* td)
   }
 }
 
-static
-int kml_altitude_known(const Waypoint* waypoint)
+int KmlFormat::kml_altitude_known(const Waypoint* waypoint) const
 {
   if (waypoint->altitude == unknown_alt) {
     return 0;
@@ -904,8 +687,7 @@ int kml_altitude_known(const Waypoint* waypoint)
   return 1;
 }
 
-static
-void kml_write_coordinates(const Waypoint* waypointp)
+void KmlFormat::kml_write_coordinates(const Waypoint* waypointp) const
 {
   if (kml_altitude_known(waypointp)) {
     writer->writeTextElement(QStringLiteral("coordinates"),
@@ -924,7 +706,7 @@ void kml_write_coordinates(const Waypoint* waypointp)
 /* Rather than a default "top down" view, view from the side to highlight
  * topo features.
  */
-static void kml_output_lookat(const Waypoint* waypointp)
+void KmlFormat::kml_output_lookat(const Waypoint* waypointp) const
 {
   writer->writeStartElement(QStringLiteral("LookAt"));
   writer->writeTextElement(QStringLiteral("longitude"), QString::number(waypointp->longitude, 'f', precision));
@@ -933,7 +715,7 @@ static void kml_output_lookat(const Waypoint* waypointp)
   writer->writeEndElement(); // Close LookAt tag
 }
 
-static void kml_output_positioning(bool tessellate)
+void KmlFormat::kml_output_positioning(bool tessellate) const
 {
   // These elements must be output as a sequence, i.e. in order.
   if (extrude) {
@@ -951,7 +733,7 @@ static void kml_output_positioning(bool tessellate)
 }
 
 /* Output something interesting when we can for route and trackpoints */
-static void kml_output_description(const Waypoint* pt)
+void KmlFormat::kml_output_description(const Waypoint* pt) const
 {
   const char* alt_units;
 
@@ -1021,7 +803,7 @@ static void kml_output_description(const Waypoint* pt)
   writer->writeEndElement(); // Close description tag
 }
 
-static void kml_recompute_time_bounds(const Waypoint* waypointp)
+void KmlFormat::kml_recompute_time_bounds(const Waypoint* waypointp)
 {
   if (waypointp->GetCreationTime().isValid()) {
     if (!(kml_time_min.isValid()) ||
@@ -1035,13 +817,13 @@ static void kml_recompute_time_bounds(const Waypoint* waypointp)
   }
 }
 
-static void kml_add_to_bounds(const Waypoint* waypointp)
+void KmlFormat::kml_add_to_bounds(const Waypoint* waypointp)
 {
   waypt_add_to_bounds(&kml_bounds, waypointp);
   kml_recompute_time_bounds(waypointp);
 }
 
-static void kml_output_point(const Waypoint* waypointp, kml_point_type pt_type)
+void KmlFormat::kml_output_point(const Waypoint* waypointp, kml_point_type pt_type) const
 {
   QString style;
 
@@ -1100,7 +882,7 @@ static void kml_output_point(const Waypoint* waypointp, kml_point_type pt_type)
   }
 }
 
-static void kml_output_tailer(const route_head* header)
+void KmlFormat::kml_output_tailer(const route_head* header)
 {
 
   if (export_points && header->rte_waypt_ct > 0) {
@@ -1178,9 +960,7 @@ static void kml_output_tailer(const route_head* header)
     writer->writeEndElement(); // Close Placemark tag
   }
 
-  if (!realtime_positioning)  {
-    writer->writeEndElement();  // Close folder tag
-  }
+  writer->writeEndElement();  // Close folder tag
 }
 
 /*
@@ -1188,8 +968,7 @@ static void kml_output_tailer(const route_head* header)
  */
 
 // Text that's common to all tabs.
-static
-void kml_gc_all_tabs_text(QString& cdataStr)
+void KmlFormat::kml_gc_all_tabs_text(QString& cdataStr)
 {
   // cdataStr.append("<a href=\"http://www.geocaching.com\"><img style=\"float: left; padding: 10px\" src=\"http://www.geocaching.com/images/nav/logo_sub.gif\" /> </a>\n");
   cdataStr.append("<img align=\"right\" src=\"$[gc_icon]\" />\n");
@@ -1201,21 +980,19 @@ void kml_gc_all_tabs_text(QString& cdataStr)
 
 }
 
-static const QString map_templates[] = {
-  "<a href=\"https://www.google.com/maps?q=$[gc_lat],$[gc_lon]\" target=\"_blank\">Google Maps</a>",
-  "<a href=\"http://www.geocaching.com/map/default.aspx?lat=$[gc_lat]&lng=$[gc_lon]\" target=\"_blank\">Geocaching.com Google Map</a>",
-  "<a href=\"http://www.mytopo.com/maps.cfm?lat=$[gc_lat]&lon=$[gc_lon]&pid=groundspeak\" target=\"_blank\">MyTopo Maps</a>",
-  "<a href=\"http://www.mapquest.com/maps/map.adp?searchtype=address&formtype=latlong&latlongtype=decimal&latitude=$[gc_lat]&longitude=$[gc_lon]&zoom=10\" target=\"_blank\">MapQuest</a>",
-  "<a href=\"http://www.bing.com/maps/default.aspx?v=2&sp=point.$[gc_lat]$[gc_lon]\" target=\"_blank\">Bing Maps</a>",
-  "<a href=\"http://maps.randmcnally.com/#s=screen&lat=$[gc_lat]&lon=$[gc_lon]&zoom=13&loc1=$[gc_lat],$[gc_lon]\" target=\"_blank\">Rand McNally</a>",
-  "<a href=\"http://www.opencyclemap.org/?zoom=12&lat=$[gc_lat]&lon=$[gc_lon]\" target=\"_blank\">Open Cycle Maps</a>",
-  "<a href=\"http://www.openstreetmap.org/?mlat=$[gc_lat]&mlon=$[gc_lon]&zoom=12\" target=\"_blank\">Open Street Maps</a>",
+const QString KmlFormat::map_templates[] = {
+  R"(<a href="https://www.google.com/maps?q=$[gc_lat],$[gc_lon]" target="_blank">Google Maps</a>)",
+  R"(<a href="http://www.geocaching.com/map/default.aspx?lat=$[gc_lat]&lng=$[gc_lon]" target="_blank">Geocaching.com Google Map</a>)",
+  R"(<a href="http://www.mytopo.com/maps.cfm?lat=$[gc_lat]&lon=$[gc_lon]&pid=groundspeak" target="_blank">MyTopo Maps</a>)",
+  R"(<a href="http://www.mapquest.com/maps/map.adp?searchtype=address&formtype=latlong&latlongtype=decimal&latitude=$[gc_lat]&longitude=$[gc_lon]&zoom=10" target="_blank">MapQuest</a>)",
+  R"(<a href="http://www.bing.com/maps/default.aspx?v=2&sp=point.$[gc_lat]$[gc_lon]" target="_blank">Bing Maps</a>)",
+  R"(<a href="http://maps.randmcnally.com/#s=screen&lat=$[gc_lat]&lon=$[gc_lon]&zoom=13&loc1=$[gc_lat],$[gc_lon]" target="_blank">Rand McNally</a>)",
+  R"(<a href="http://www.opencyclemap.org/?zoom=12&lat=$[gc_lat]&lon=$[gc_lon]" target="_blank">Open Cycle Maps</a>)",
+  R"(<a href="http://www.openstreetmap.org/?mlat=$[gc_lat]&mlon=$[gc_lon]&zoom=12" target="_blank">Open Street Maps</a>)",
   nullptr
 };
 
-
-static
-void kml_gc_make_balloonstyletext()
+void KmlFormat::kml_gc_make_balloonstyletext() const
 {
   QString cdataStr;
 
@@ -1267,7 +1044,7 @@ void kml_gc_make_balloonstyletext()
   cdataStr.append("  <ul>\n");
   // Fortunately, all the mappy map URLs take lat/longs in the URLs, so
   // the substitution is easy.
-  for (int tp = 0; !map_templates[tp].isEmpty(); tp++) {
+  for (int tp = 0; !map_templates[tp].isEmpty(); ++tp) {
     cdataStr.append("    <li>\n");
     cdataStr.append("    ");
     cdataStr.append(map_templates[tp]);
@@ -1286,8 +1063,7 @@ void kml_gc_make_balloonstyletext()
   writer->writeEndElement(); // Close BalloonStyle tag
 }
 
-static
-void kml_gc_make_balloonstyle()
+void KmlFormat::kml_gc_make_balloonstyle() const
 {
   // For Normal style of gecoaches, scale of label is set to zero
   // to make the label invisible.  On hover (highlight?) enlarge
@@ -1334,9 +1110,7 @@ void kml_gc_make_balloonstyle()
   writer->writeEndElement(); // Close StyleMap tag
 }
 
-static
-QString
-kml_lookup_gc_icon(const Waypoint* waypointp)
+QString KmlFormat::kml_lookup_gc_icon(const Waypoint* waypointp)
 {
   const char* icon;
   /* This could be done so much better in C99 with designated
@@ -1390,9 +1164,7 @@ kml_lookup_gc_icon(const Waypoint* waypointp)
   return QString("https://www.geocaching.com/images/kml/%1").arg(icon);
 }
 
-static const
-char*
-kml_lookup_gc_container(const Waypoint* waypointp)
+const char* KmlFormat::kml_lookup_gc_container(const Waypoint* waypointp)
 {
   const char* cont;
 
@@ -1423,7 +1195,7 @@ kml_lookup_gc_container(const Waypoint* waypointp)
   return cont;
 }
 
-static QString kml_gc_mkstar(int rating)
+QString KmlFormat::kml_gc_mkstar(int rating)
 {
   QString star_content;
 
@@ -1441,7 +1213,7 @@ static QString kml_gc_mkstar(int rating)
 
 }
 
-static QString kml_geocache_get_logs(const Waypoint* wpt)
+QString KmlFormat::kml_geocache_get_logs(const Waypoint* wpt) const
 {
   QString r;
 
@@ -1498,7 +1270,7 @@ static QString kml_geocache_get_logs(const Waypoint* wpt)
   return r;
 }
 
-static void kml_write_data_element(const QString& name, const QString& value)
+void KmlFormat::kml_write_data_element(const QString& name, const QString& value) const
 {
   writer->writeStartElement(QStringLiteral("Data"));
   writer->writeAttribute(QStringLiteral("name"), name);
@@ -1506,7 +1278,7 @@ static void kml_write_data_element(const QString& name, const QString& value)
   writer->writeEndElement(); // Close Data tag
 }
 
-static void kml_write_data_element(const QString& name, const int value)
+void KmlFormat::kml_write_data_element(const QString& name, const int value) const
 {
   writer->writeStartElement(QStringLiteral("Data"));
   writer->writeAttribute(QStringLiteral("name"), name);
@@ -1514,7 +1286,7 @@ static void kml_write_data_element(const QString& name, const int value)
   writer->writeEndElement(); // Close Data tag
 }
 
-static void kml_write_data_element(const QString& name, const double value)
+void KmlFormat::kml_write_data_element(const QString& name, const double value) const
 {
   writer->writeStartElement(QStringLiteral("Data"));
   writer->writeAttribute(QStringLiteral("name"), name);
@@ -1522,7 +1294,7 @@ static void kml_write_data_element(const QString& name, const double value)
   writer->writeEndElement(); // Close Data tag
 }
 
-static void kml_write_cdata_element(const QString& name, const QString& value)
+void KmlFormat::kml_write_cdata_element(const QString& name, const QString& value) const
 {
   writer->writeStartElement(QStringLiteral("Data"));
   writer->writeAttribute(QStringLiteral("name"), name);
@@ -1532,7 +1304,7 @@ static void kml_write_cdata_element(const QString& name, const QString& value)
   writer->writeEndElement(); // Close Data tag
 }
 
-static void kml_geocache_pr(const Waypoint* waypointp)
+void KmlFormat::kml_geocache_pr(const Waypoint* waypointp) const
 {
   const char* issues = "";
 
@@ -1617,7 +1389,7 @@ static void kml_geocache_pr(const Waypoint* waypointp)
  * WAYPOINTS
  */
 
-static void kml_waypt_pr(const Waypoint* waypointp)
+void KmlFormat::kml_waypt_pr(const Waypoint* waypointp) const
 {
   QString icon;
 
@@ -1689,7 +1461,7 @@ static void kml_waypt_pr(const Waypoint* waypointp)
  * TRACKPOINTS
  */
 
-static void kml_track_hdr(const route_head* header)
+void KmlFormat::kml_track_hdr(const route_head* header) const
 {
   computed_trkdata td = track_recompute(header);
   if (header->rte_waypt_ct > 0 && (export_lines || export_points)) {
@@ -1697,12 +1469,12 @@ static void kml_track_hdr(const route_head* header)
   }
 }
 
-static void kml_track_disp(const Waypoint* waypointp)
+void KmlFormat::kml_track_disp(const Waypoint* waypointp) const
 {
   kml_output_point(waypointp, kmlpt_track);
 }
 
-static void kml_track_tlr(const route_head* header)
+void KmlFormat::kml_track_tlr(const route_head* header)
 {
   if (header->rte_waypt_ct > 0 && (export_lines || export_points)) {
     kml_output_tailer(header);
@@ -1715,19 +1487,9 @@ static void kml_track_tlr(const route_head* header)
  * callback as we have to make multiple passes over the track queues.
  */
 
-// Helper to write gx:SimpleList, iterating over a route queue and writing out.
-
-enum wp_field {
-  fld_cadence,
-  fld_depth,
-  fld_heartrate,
-  fld_temperature,
-  fld_power
-};
-
-static void kml_mt_simple_array(const route_head* header,
-                                const char* name,
-                                wp_field member)
+void KmlFormat::kml_mt_simple_array(const route_head* header,
+                                    const char* name,
+                                    wp_field member) const
 {
   writer->writeStartElement(QStringLiteral("gx:SimpleArrayData"));
   writer->writeAttribute(QStringLiteral("name"), name);
@@ -1758,7 +1520,7 @@ static void kml_mt_simple_array(const route_head* header,
 }
 
 // True if at least two points in the track have timestamps.
-static int track_has_time(const route_head* header)
+int KmlFormat::track_has_time(const route_head* header)
 {
   int points_with_time = 0;
   foreach (const Waypoint* tpt, header->waypoint_list) {
@@ -1773,7 +1535,7 @@ static int track_has_time(const route_head* header)
 }
 
 // Simulate a track_disp_all callback sequence for a single track.
-static void write_as_linestring(const route_head* header)
+void KmlFormat::write_as_linestring(const route_head* header)
 {
   kml_track_hdr(header);
   foreach (const Waypoint* tpt, header->waypoint_list) {
@@ -1783,7 +1545,7 @@ static void write_as_linestring(const route_head* header)
 
 }
 
-static void kml_mt_hdr(const route_head* header)
+void KmlFormat::kml_mt_hdr(const route_head* header)
 {
   int has_cadence = 0;
   int has_depth = 0;
@@ -1879,7 +1641,7 @@ static void kml_mt_hdr(const route_head* header)
   }
 }
 
-static void kml_mt_tlr(const route_head* header)
+void KmlFormat::kml_mt_tlr(const route_head* header) const
 {
   if (track_has_time(header)) {
     writer->writeEndElement(); // Close gx:Track tag
@@ -1891,17 +1653,17 @@ static void kml_mt_tlr(const route_head* header)
  * ROUTES
  */
 
-static void kml_route_hdr(const route_head* header)
+void KmlFormat::kml_route_hdr(const route_head* header) const
 {
   kml_output_header(header, nullptr);
 }
 
-static void kml_route_disp(const Waypoint* waypointp)
+void KmlFormat::kml_route_disp(const Waypoint* waypointp) const
 {
   kml_output_point(waypointp, kmlpt_route);
 }
 
-static void kml_route_tlr(const route_head* header)
+void KmlFormat::kml_route_tlr(const route_head* header)
 {
   kml_output_tailer(header);
 }
@@ -1909,17 +1671,20 @@ static void kml_route_tlr(const route_head* header)
 // For Earth 5.0 and later, we write a LookAt that encompasses
 // the bounding box of our entire data set and set the event times
 // to include all our data.
-static void kml_write_AbstractView()
+void KmlFormat::kml_write_AbstractView()
 {
   // Make a pass through all the points to find the bounds.
+  auto kml_add_to_bounds_lambda = [this](const Waypoint* waypointp)->void {
+    kml_add_to_bounds(waypointp);
+  };
   if (waypt_count()) {
-    waypt_disp_all(kml_add_to_bounds);
+    waypt_disp_all(kml_add_to_bounds_lambda);
   }
   if (track_waypt_count())  {
-    track_disp_all(nullptr, nullptr, kml_add_to_bounds);
+    track_disp_all(nullptr, nullptr, kml_add_to_bounds_lambda);
   }
   if (route_waypt_count()) {
-    route_disp_all(nullptr, nullptr, kml_add_to_bounds);
+    route_disp_all(nullptr, nullptr, kml_add_to_bounds_lambda);
   }
 
   writer->writeStartElement(QStringLiteral("LookAt"));
@@ -1937,9 +1702,8 @@ static void kml_write_AbstractView()
       // the network position.  So we shove the end of the timespan out to
       // ensure the right edge of that time slider includes us.
       //
-      gpsbabel::DateTime time_max;
-      time_max = realtime_positioning ? kml_time_max.addSecs(600)
-                                      : kml_time_max;
+      gpsbabel::DateTime time_max = realtime_positioning ? kml_time_max.addSecs(600)
+                                    : kml_time_max;
       writer->writeTextElement(QStringLiteral("end"), time_max.toPrettyString());
     }
     writer->writeEndElement(); // Close gx:TimeSpan tag
@@ -1968,9 +1732,8 @@ static void kml_write_AbstractView()
   writer->writeEndElement(); // Close LookAt tag
 }
 
-static
-void kml_mt_array_schema(const char* field_name, const char* display_name,
-                         const char* type)
+void KmlFormat::kml_mt_array_schema(const char* field_name, const char* display_name,
+                                    const char* type) const
 {
   writer->writeStartElement(QStringLiteral("gx:SimpleArrayField"));
   writer->writeAttribute(QStringLiteral("name"), field_name);
@@ -1979,7 +1742,7 @@ void kml_mt_array_schema(const char* field_name, const char* display_name,
   writer->writeEndElement(); // Close gx:SimpleArrayField tag
 }
 
-static void kml_write()
+void KmlFormat::write()
 {
   const global_trait* traits = get_traits();
 
@@ -2031,7 +1794,7 @@ static void kml_write()
   if (track_waypt_count()) {
     if (trackdirection) {
       kml_write_bitmap_style(kmlpt_other, ICON_TRK, "track-none");
-      for (int i = 0; i < 16; i++) {
+      for (int i = 0; i < 16; ++i) {
         kml_write_bitmap_style(kmlpt_other, QString(ICON_DIR).arg(i), QString("track-%1").arg(i));
       }
     } else {
@@ -2087,7 +1850,10 @@ static void kml_write()
       writer->writeTextElement(QStringLiteral("name"), QStringLiteral("Waypoints"));
     }
 
-    waypt_disp_all(kml_waypt_pr);
+    auto kml_waypt_pr_lambda = [this](const Waypoint* waypointp)->void {
+      kml_waypt_pr(waypointp);
+    };
+    waypt_disp_all(kml_waypt_pr_lambda);
 
     if (!realtime_positioning) {
       writer->writeEndElement(); // Close Folder tag
@@ -2103,11 +1869,26 @@ static void kml_write()
 
     kml_init_color_sequencer(track_count());
     if (export_track) {
-      track_disp_all(kml_mt_hdr, kml_mt_tlr, nullptr);
+      auto kml_mt_hdr_lambda = [this](const route_head* rte)->void {
+        kml_mt_hdr(rte);
+      };
+      auto kml_mt_tlr_lambda = [this](const route_head* rte)->void {
+        kml_mt_tlr(rte);
+      };
+      track_disp_all(kml_mt_hdr_lambda, kml_mt_tlr_lambda, nullptr);
     }
 
-    track_disp_all(kml_track_hdr, kml_track_tlr,
-                   kml_track_disp);
+    auto kml_track_hdr_lambda = [this](const route_head* rte)->void {
+      kml_track_hdr(rte);
+    };
+    auto kml_track_tlr_lambda = [this](const route_head* rte)->void {
+      kml_track_tlr(rte);
+    };
+    auto kml_track_disp_lambda = [this](const Waypoint* waypointp)->void {
+      kml_track_disp(waypointp);
+    };
+    track_disp_all(kml_track_hdr_lambda, kml_track_tlr_lambda,
+                   kml_track_disp_lambda);
 
     if (!realtime_positioning) {
       writer->writeEndElement(); // Close Folder tag
@@ -2121,8 +1902,17 @@ static void kml_write()
       writer->writeTextElement(QStringLiteral("name"), QStringLiteral("Routes"));
 
       kml_init_color_sequencer(route_count());
-      route_disp_all(kml_route_hdr,
-                     kml_route_tlr, kml_route_disp);
+      auto kml_route_hdr_lambda = [this](const route_head* rte)->void {
+        kml_route_hdr(rte);
+      };
+      auto kml_route_tlr_lambda = [this](const route_head* rte)->void {
+        kml_route_tlr(rte);
+      };
+      auto kml_route_disp_lambda = [this](const Waypoint* waypointp)->void {
+        kml_route_disp(waypointp);
+      };
+      route_disp_all(kml_route_hdr_lambda,
+                     kml_route_tlr_lambda, kml_route_disp_lambda);
       writer->writeEndElement(); // Close Folder tag
     }
   }
@@ -2134,32 +1924,35 @@ static void kml_write()
 /*
  * This depends on the table being sorted correctly.
  */
-static const
-char*
-kml_get_posn_icon(int freshness)
-{
-  int n_stations = sizeof(kml_tracking_icons) / sizeof(kml_tracking_icons[0]);
-
-  for (int i = 0; i < n_stations ; i++) {
-    if (freshness >= kml_tracking_icons[i].freshness) {
-      return kml_tracking_icons[i].icon;
+QString KmlFormat::kml_get_posn_icon(int freshness)
+{
+  struct kml_tracking_icon {
+    int freshness;
+    QString icon;
+  };
+  static const QVector<kml_tracking_icon> kml_tracking_icons = {
+    { 60, ICON_BASE "youarehere-60.png" }, // Red
+    { 30, ICON_BASE "youarehere-30.png" }, // Yellow
+    { 0,  ICON_BASE "youarehere-0.png" }, // Green
+  };
+
+  for (const auto& entry : kml_tracking_icons) {
+    if (freshness >= entry.freshness) {
+      return entry.icon;
     }
   }
   return ICON_NOSAT;
 }
 
-
-static route_head* posn_trk_head = nullptr;
-
-static void
-kml_wr_position(Waypoint* wpt)
+void KmlFormat::wr_position(Waypoint* wpt)
 {
   static gpsbabel::DateTime last_valid_fix;
 
-  kml_wr_init(posnfilenametmp);
+  wr_init(posnfilenametmp);
 
   if (!posn_trk_head) {
     posn_trk_head = new route_head;
+    posn_trk_head->rte_name = "Track";
     track_add_head(posn_trk_head);
   }
 
@@ -2192,7 +1985,9 @@ kml_wr_position(Waypoint* wpt)
   /* In order to avoid clutter while we're sitting still, don't add
      track points if we've not moved a minimum distance from the
      beginning of our accumulated track. */
-  {
+  if (posn_trk_head->waypoint_list.empty()) {
+    track_add_wpt(posn_trk_head, new Waypoint(*wpt));
+  } else {
     Waypoint* newest_posn= posn_trk_head->waypoint_list.back();
 
     if (radtometers(gcdist(RAD(wpt->latitude), RAD(wpt->longitude),
@@ -2209,7 +2004,7 @@ kml_wr_position(Waypoint* wpt)
   }
 
   waypt_add(wpt);
-  kml_write();
+  write();
   waypt_del(wpt);
 
   /*
@@ -2222,21 +2017,5 @@ kml_wr_position(Waypoint* wpt)
     track_del_wpt(posn_trk_head, tonuke);
   }
 
-  kml_wr_deinit();
+  wr_deinit();
 }
-
-ff_vecs_t kml_vecs = {
-  ff_type_file,
-  FF_CAP_RW_ALL, /* Format can do RW_ALL */
-  kml_rd_init,
-  kml_wr_init,
-  kml_rd_deinit,
-  kml_wr_deinit,
-  kml_read,
-  kml_write,
-  nullptr,
-  &kml_args,
-  CET_CHARSET_UTF8, 1, /* CET-REVIEW */
-  { nullptr, nullptr, nullptr, kml_wr_position_init, kml_wr_position, kml_wr_position_deinit },
-  nullptr
-};
diff --git a/kml.h b/kml.h
new file mode 100644 (file)
index 0000000..edf2a4e
--- /dev/null
+++ b/kml.h
@@ -0,0 +1,348 @@
+/*
+       Support for Google Earth & Keyhole "kml" format.
+
+       Copyright (C) 2005-2013 Robert Lipe, robertlipe+source@gpsbabel.org
+       Updates by Andrew Kirmse, akirmse at google.com
+
+       This program is free software; you can redistribute it and/or modify
+       it under the terms of the GNU General Public License as published by
+       the Free Software Foundation; either version 2 of the License, or
+       (at your option) any later version.
+
+       This program is distributed in the hope that it will be useful,
+       but WITHOUT ANY WARRANTY; without even the implied warranty of
+       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+       GNU General Public License for more details.
+
+       You should have received a copy of the GNU General Public License
+       along with this program; if not, write to the Free Software
+       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+ */
+#ifndef KML_H_INCLUDED_
+#define KML_H_INCLUDED_
+
+#ifdef __WIN32__
+# include <windows.h>
+#endif
+
+#include <tuple>                        // for tuple, make_tuple, tie
+
+#include <QtCore/QList>                 // for QList
+#include <QtCore/QString>               // for QString, QStringLiteral, operator+, operator!=
+#include <QtCore/QVector>               // for QVector
+#include <QtCore/QXmlStreamAttributes>  // for QXmlStreamAttributes
+
+#include "defs.h"
+#include "format.h"
+#include "src/core/datetime.h"          // for DateTime
+#include "src/core/file.h"              // for File
+#include "src/core/xmlstreamwriter.h"   // for XmlStreamWriter
+#include "xmlgeneric.h"                 // for cb_cdata, cb_end, cb_start, xg_callback, xg_string, xg_cb_type, xml_deinit, xml_ignore_tags, xml_init, xml_read, xg_tag_mapping
+
+
+class KmlFormat : public Format
+{
+public:
+  QVector<arglist_t>* get_args() override
+  {
+    return &kml_args;
+  }
+
+  ff_type get_type() const override
+  {
+    return ff_type_file;
+  }
+
+  QVector<ff_cap> get_cap() const override
+  {
+    return FF_CAP_RW_ALL;
+  }
+
+  QString get_encode() const override
+  {
+    return CET_CHARSET_UTF8;
+  }
+
+  int get_fixed_encode() const override
+  {
+    return 1;
+  }
+
+  void rd_init(const QString& fname) override;
+  void read() override;
+  void rd_deinit() override;
+  void wr_init(const QString& fname) override;
+  void write() override;
+  void wr_deinit() override;
+  void wr_position_init(const QString& fname) override;
+  void wr_position(Waypoint* wpt) override;
+  void wr_position_deinit() override;
+
+private:
+  /* Types */
+
+  enum kml_point_type {
+    kmlpt_unknown,
+    kmlpt_waypoint,
+    kmlpt_track,
+    kmlpt_route,
+    kmlpt_multitrack,
+    kmlpt_other
+  };
+
+// Helper to write gx:SimpleList, iterating over a route queue and writing out.
+
+  enum wp_field {
+    fld_cadence,
+    fld_depth,
+    fld_heartrate,
+    fld_temperature,
+    fld_power
+  };
+
+  /* Constants */
+  static constexpr const char* default_precision = "6";
+  static constexpr int kml_color_limit = 204;  /* allowed range [0,255] */
+
+  // Multitrack ids to correlate Schema to SchemaData
+  static constexpr const char* kmt_heartrate = "heartrate";
+  static constexpr const char* kmt_cadence = "cadence";
+  static constexpr const char* kmt_temperature = "temperature";
+  static constexpr const char* kmt_depth = "depth";
+  static constexpr const char* kmt_power = "power";
+
+  /* Member Functions */
+
+  void kml_init_color_sequencer(unsigned int steps_per_rev);
+  void kml_step_color();
+  void wpt_s(const QString& args, const QXmlStreamAttributes* attrs);
+  void wpt_e(const QString& args, const QXmlStreamAttributes* attrs);
+  void wpt_name(const QString& args, const QXmlStreamAttributes* attrs);
+  void wpt_desc(const QString& args, const QXmlStreamAttributes* attrs);
+  void wpt_coord(const QString& args, const QXmlStreamAttributes* attrs);
+  void wpt_icon(const QString& args, const QXmlStreamAttributes* attrs);
+  void trk_coord(const QString& args, const QXmlStreamAttributes* attrs);
+  void wpt_time(const QString& args, const QXmlStreamAttributes* attrs);
+  void wpt_ts_begin(const QString& args, const QXmlStreamAttributes* attrs);
+  void wpt_ts_end(const QString& args, const QXmlStreamAttributes* attrs);
+  void gx_trk_s(const QString& args, const QXmlStreamAttributes* attrs);
+  void gx_trk_e(const QString& args, const QXmlStreamAttributes* attrs);
+  void gx_trk_when(const QString& args, const QXmlStreamAttributes* attrs);
+  void gx_trk_coord(const QString& args, const QXmlStreamAttributes* attrs);
+  void kml_output_linestyle(char* color, int width) const;
+  void kml_write_bitmap_style_(const QString& style, const QString& bitmap, int highlighted, int force_heading) const;
+  void kml_write_bitmap_style(kml_point_type pt_type, const QString& bitmap, const QString& customstyle) const;
+  void kml_output_timestamp(const Waypoint* waypointp) const;
+  static void kml_td(gpsbabel::XmlStreamWriter& hwriter, const QString& boldData, const QString& data);
+  static void kml_td(gpsbabel::XmlStreamWriter& hwriter, const QString& data);
+  void kml_output_trkdescription(const route_head* header, const computed_trkdata* td) const;
+  void kml_output_header(const route_head* header, const computed_trkdata* td) const;
+  int kml_altitude_known(const Waypoint* waypoint) const;
+  void kml_write_coordinates(const Waypoint* waypointp) const;
+  void kml_output_lookat(const Waypoint* waypointp) const;
+  void kml_output_positioning(bool tessellate) const;
+  void kml_output_description(const Waypoint* pt) const;
+  void kml_recompute_time_bounds(const Waypoint* waypointp);
+  void kml_add_to_bounds(const Waypoint* waypointp);
+  void kml_output_point(const Waypoint* waypointp, kml_point_type pt_type) const;
+  void kml_output_tailer(const route_head* header);
+  static void kml_gc_all_tabs_text(QString& cdataStr);
+  void kml_gc_make_balloonstyletext() const;
+  void kml_gc_make_balloonstyle() const;
+  static QString kml_lookup_gc_icon(const Waypoint* waypointp);
+  static const char* kml_lookup_gc_container(const Waypoint* waypointp);
+  static QString kml_gc_mkstar(int rating);
+  QString kml_geocache_get_logs(const Waypoint* wpt) const;
+  void kml_write_data_element(const QString& name, const QString& value) const;
+  void kml_write_data_element(const QString& name, int value) const;
+  void kml_write_data_element(const QString& name, double value) const;
+  void kml_write_cdata_element(const QString& name, const QString& value) const;
+  void kml_geocache_pr(const Waypoint* waypointp) const;
+  void kml_waypt_pr(const Waypoint* waypointp) const;
+  void kml_track_hdr(const route_head* header) const;
+  void kml_track_disp(const Waypoint* waypointp) const;
+  void kml_track_tlr(const route_head* header);
+  void kml_mt_simple_array(const route_head* header, const char* name, wp_field member) const;
+  static int track_has_time(const route_head* header);
+  void write_as_linestring(const route_head* header);
+  void kml_mt_hdr(const route_head* header);
+  void kml_mt_tlr(const route_head* header) const;
+  void kml_route_hdr(const route_head* header) const;
+  void kml_route_disp(const Waypoint* waypointp) const;
+  void kml_route_tlr(const route_head* header);
+  void kml_write_AbstractView();
+  void kml_mt_array_schema(const char* field_name, const char* display_name, const char* type) const;
+  static QString kml_get_posn_icon(int freshness);
+
+  /* Data Members */
+
+  // options
+  char* opt_deficon{nullptr};
+  char* opt_export_lines{nullptr};
+  char* opt_export_points{nullptr};
+  char* opt_export_track{nullptr};
+  char* opt_line_width{nullptr};
+  char* opt_line_color{nullptr};
+  char* opt_floating{nullptr};
+  char* opt_extrude{nullptr};
+  char* opt_trackdata{nullptr};
+  char* opt_trackdirection{nullptr};
+  char* opt_units{nullptr};
+  char* opt_labels{nullptr};
+  char* opt_max_position_points{nullptr};
+  char* opt_rotate_colors{nullptr};
+  char* opt_precision{nullptr};
+
+  int export_lines{};
+  int export_points{};
+  int export_track{};
+  int floating{};
+  int extrude{};
+  int trackdata{};
+  int trackdirection{};
+  int max_position_points{};
+  int rotate_colors{};
+  int line_width{};
+  int html_encrypt{};
+  int precision{};
+
+  Waypoint* wpt_tmp{nullptr};
+  bool wpt_tmp_queued{false};
+  QString posnfilename;
+  QString posnfilenametmp;
+
+  route_head* gx_trk_head{nullptr};
+  QList<gpsbabel::DateTime>* gx_trk_times{nullptr};
+  QList<std::tuple<int, double, double, double>>* gx_trk_coords{nullptr};
+
+  gpsbabel::File* oqfile{nullptr};
+  gpsbabel::XmlStreamWriter* writer{nullptr};
+
+  int realtime_positioning{};
+  bounds kml_bounds{};
+  gpsbabel::DateTime kml_time_max;
+  gpsbabel::DateTime kml_time_min;
+
+  QVector<arglist_t> kml_args = {
+    {"deficon", &opt_deficon, "Default icon name", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr },
+    {
+      "lines", &opt_export_lines,
+      "Export linestrings for tracks and routes",
+      "1", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr,
+    },
+    {
+      "points", &opt_export_points,
+      "Export placemarks for tracks and routes",
+      "1", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
+    },
+    {
+      "line_width", &opt_line_width,
+      "Width of lines, in pixels",
+      "6", ARGTYPE_INT, ARG_NOMINMAX, nullptr
+    },
+    {
+      "line_color", &opt_line_color,
+      "Line color, specified in hex AABBGGRR",
+      "99ffac59", ARGTYPE_STRING, ARG_NOMINMAX, nullptr
+    },
+    {
+      "floating", &opt_floating,
+      "Altitudes are absolute and not clamped to ground",
+      "0", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
+    },
+    {
+      "extrude", &opt_extrude,
+      "Draw extrusion line from trackpoint to ground",
+      "0", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
+    },
+    {
+      "track", &opt_export_track,
+      "Write KML track (default = 0)",
+      "0", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
+    },
+    {
+      "trackdata", &opt_trackdata,
+      "Include extended data for trackpoints (default = 1)",
+      "1", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
+    },
+    {
+      "trackdirection", &opt_trackdirection,
+      "Indicate direction of travel in track icons (default = 0)",
+      "0", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
+    },
+    {
+      "units", &opt_units,
+      "Units used when writing comments ('s'tatute, 'm'etric,' 'n'autical, 'a'viation)",
+      "s", ARGTYPE_STRING, ARG_NOMINMAX, nullptr
+    },
+    {
+      "labels", &opt_labels,
+      "Display labels on track and routepoints  (default = 1)",
+      "1", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
+    },
+    {
+      "max_position_points", &opt_max_position_points,
+      "Retain at most this number of position points  (0 = unlimited)",
+      "0", ARGTYPE_INT, ARG_NOMINMAX, nullptr
+    },
+    {
+      "rotate_colors", &opt_rotate_colors,
+      "Rotate colors for tracks and routes (default automatic)",
+      nullptr, ARGTYPE_FLOAT, "0", "360", nullptr
+    },
+    {
+      "prec", &opt_precision,
+      "Precision of coordinates, number of decimals",
+      default_precision, ARGTYPE_INT, ARG_NOMINMAX, nullptr
+    },
+  };
+
+  struct {
+    float seq{0.0f};
+    float step{0.0f};
+    gb_color color;
+  } kml_color_sequencer;
+
+  QList<xg_functor_map_entry<KmlFormat>> kml_map = {
+    {&KmlFormat::wpt_s, cb_start, "/Placemark"},
+    {&KmlFormat::wpt_e, cb_end, "/Placemark"},
+    {&KmlFormat::wpt_name, cb_cdata, "/Placemark/name"},
+    {&KmlFormat::wpt_desc, cb_cdata, "/Placemark/description"},
+    {&KmlFormat::wpt_ts_begin, cb_cdata,"/Placemark/TimeSpan/begin"},
+    {&KmlFormat::wpt_ts_end, cb_cdata, "/Placemark/TimeSpan/end"},
+    {&KmlFormat::wpt_time, cb_cdata, "/Placemark/TimeStamp/when"},
+    // Alias for above used in KML 2.0
+    {&KmlFormat::wpt_time, cb_cdata, "/Placemark/TimeInstant/timePosition"},
+    {&KmlFormat::wpt_coord, cb_cdata, "/Placemark/Point/coordinates"},
+    {&KmlFormat::wpt_icon, cb_cdata, "/Placemark/Style/Icon/href"},
+    {&KmlFormat::trk_coord, cb_cdata, "/Placemark/MultiGeometry/LineString/coordinates"},
+    {&KmlFormat::trk_coord, cb_cdata, "/Placemark/GeometryCollection/LineString/coordinates"},
+    {&KmlFormat::trk_coord, cb_cdata, "/Placemark/Polygon/outerBoundaryIs/LinearRing/coordinates"},
+    {&KmlFormat::trk_coord, cb_cdata, "/Placemark/LineString/coordinates"},
+    {&KmlFormat::gx_trk_s,  cb_start, "/Placemark/*gx:Track"},
+    {&KmlFormat::gx_trk_e,  cb_end, "/Placemark/*gx:Track"},
+    {&KmlFormat::gx_trk_when,  cb_cdata, "/Placemark/*gx:Track/when"},
+    {&KmlFormat::gx_trk_coord, cb_cdata, "/Placemark/*gx:Track/gx:coord"},
+    {&KmlFormat::gx_trk_s,  cb_start, "/Placemark/Track"}, // KML 2.3
+    {&KmlFormat::gx_trk_e,  cb_end, "/Placemark/Track"}, // KML 2.3
+    {&KmlFormat::gx_trk_when,  cb_cdata, "/Placemark/Track/when"}, // KML 2.3
+    {&KmlFormat::gx_trk_coord, cb_cdata, "/Placemark/Track/coord"}, // KML 2.3
+    {&KmlFormat::gx_trk_s,  cb_start, "/Placemark/MultiTrack/Track"}, // KML 2.3
+    {&KmlFormat::gx_trk_e,  cb_end, "/Placemark/MultiTrack/Track"}, // KML 2.3
+    {&KmlFormat::gx_trk_when,  cb_cdata, "/Placemark/MultiTrack/Track/when"}, // KML 2.3
+    {&KmlFormat::gx_trk_coord, cb_cdata, "/Placemark/MultiTrack/Track/coord"} // KML 2.3
+  };
+
+  static const char* kml_tags_to_ignore[];
+  static const char* kml_tags_to_skip[];
+
+  // The TimeSpan/begin and TimeSpan/end DateTimes:
+  gpsbabel::DateTime wpt_timespan_begin, wpt_timespan_end;
+
+  static const QString map_templates[];
+
+  route_head* posn_trk_head{nullptr};
+};
+
+#endif // KML_H_INCLUDED_
diff --git a/reference/realtime.kml b/reference/realtime.kml
new file mode 100644 (file)
index 0000000..bca1614
--- /dev/null
@@ -0,0 +1,803 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2">
+  <Document>
+    <name>GPS position</name>
+    <LookAt>
+      <gx:TimeSpan>
+        <begin>1970-01-01T00:00:03.464Z</begin>
+        <end>1970-01-01T00:10:29.519Z</end>
+      </gx:TimeSpan>
+      <longitude>-32.056227</longitude>
+      <latitude>30.474664</latitude>
+      <range>1728.780603</range>
+    </LookAt>
+    <!-- Normal track style -->
+    <Style id="track_n">
+      <IconStyle>
+        <scale>.5</scale>
+        <Icon>
+          <href>https://earth.google.com/images/kml-icons/track-directional/track-none.png</href>
+        </Icon>
+      </IconStyle>
+      <LabelStyle>
+        <scale>0</scale>
+      </LabelStyle>
+    </Style>
+    <!-- Highlighted track style -->
+    <Style id="track_h">
+      <IconStyle>
+        <scale>1.2</scale>
+        <Icon>
+          <href>https://earth.google.com/images/kml-icons/track-directional/track-none.png</href>
+        </Icon>
+      </IconStyle>
+    </Style>
+    <StyleMap id="track">
+      <Pair>
+        <key>normal</key>
+        <styleUrl>#track_n</styleUrl>
+      </Pair>
+      <Pair>
+        <key>highlight</key>
+        <styleUrl>#track_h</styleUrl>
+      </Pair>
+    </StyleMap>
+    <!-- Normal multiTrack style -->
+    <Style id="multiTrack_n">
+      <IconStyle>
+        <Icon>
+          <href>https://earth.google.com/images/kml-icons/track-directional/track-0.png</href>
+        </Icon>
+      </IconStyle>
+      <LineStyle>
+        <color>99ffac59</color>
+        <width>6</width>
+      </LineStyle>
+    </Style>
+    <!-- Highlighted multiTrack style -->
+    <Style id="multiTrack_h">
+      <IconStyle>
+        <scale>1.2</scale>
+        <Icon>
+          <href>https://earth.google.com/images/kml-icons/track-directional/track-0.png</href>
+        </Icon>
+      </IconStyle>
+      <LineStyle>
+        <color>99ffac59</color>
+        <width>8</width>
+      </LineStyle>
+    </Style>
+    <StyleMap id="multiTrack">
+      <Pair>
+        <key>normal</key>
+        <styleUrl>#multiTrack_n</styleUrl>
+      </Pair>
+      <Pair>
+        <key>highlight</key>
+        <styleUrl>#multiTrack_h</styleUrl>
+      </Pair>
+    </StyleMap>
+    <!-- Normal waypoint style -->
+    <Style id="waypoint_n">
+      <IconStyle>
+        <Icon>
+          <href>https://maps.google.com/mapfiles/kml/pal4/icon61.png</href>
+        </Icon>
+      </IconStyle>
+    </Style>
+    <!-- Highlighted waypoint style -->
+    <Style id="waypoint_h">
+      <IconStyle>
+        <scale>1.2</scale>
+        <Icon>
+          <href>https://maps.google.com/mapfiles/kml/pal4/icon61.png</href>
+        </Icon>
+      </IconStyle>
+    </Style>
+    <StyleMap id="waypoint">
+      <Pair>
+        <key>normal</key>
+        <styleUrl>#waypoint_n</styleUrl>
+      </Pair>
+      <Pair>
+        <key>highlight</key>
+        <styleUrl>#waypoint_h</styleUrl>
+      </Pair>
+    </StyleMap>
+    <Style id="lineStyle">
+      <LineStyle>
+        <color>99ffac59</color>
+        <width>6</width>
+      </LineStyle>
+    </Style>
+    <Schema id="schema">
+      <gx:SimpleArrayField name="heartrate" type="int">
+        <displayName>Heart Rate</displayName>
+      </gx:SimpleArrayField>
+      <gx:SimpleArrayField name="cadence" type="int">
+        <displayName>Cadence</displayName>
+      </gx:SimpleArrayField>
+      <gx:SimpleArrayField name="temperature" type="float">
+        <displayName>Temperature</displayName>
+      </gx:SimpleArrayField>
+      <gx:SimpleArrayField name="depth" type="float">
+        <displayName>Depth</displayName>
+      </gx:SimpleArrayField>
+    </Schema>
+    <Placemark>
+      <name>Wpt_hCT</name>
+      <snippet/>
+      <description>http://link1.example.com/q31hGd</description>
+      <TimeStamp>
+        <when>1970-01-01T00:00:29.519Z</when>
+      </TimeStamp>
+      <Style>
+        <IconStyle>
+          <Icon>
+            <href>https://earth.google.com/images/kml-icons/youarehere-0.png</href>
+          </Icon>
+        </IconStyle>
+      </Style>
+      <Point>
+        <coordinates>-32.051141,30.478722</coordinates>
+      </Point>
+    </Placemark>
+    <Placemark>
+      <name>Track</name>
+      <styleUrl>#multiTrack</styleUrl>
+      <gx:Track>
+        <when/>
+        <when>1970-01-01T00:00:03.464Z</when>
+        <when>1970-01-01T00:00:04.834Z</when>
+        <when>1970-01-01T00:00:06.126Z</when>
+        <when>1970-01-01T00:00:07.686Z</when>
+        <when>1970-01-01T00:00:09.493Z</when>
+        <when>1970-01-01T00:00:12.434Z</when>
+        <when>1970-01-01T00:00:14.191Z</when>
+        <when>1970-01-01T00:00:16.984Z</when>
+        <when>1970-01-01T00:00:18.913Z</when>
+        <when>1970-01-01T00:00:20.628Z</when>
+        <when>1970-01-01T00:00:22.594Z</when>
+        <when>1970-01-01T00:00:23.968Z</when>
+        <when>1970-01-01T00:00:25.135Z</when>
+        <when>1970-01-01T00:00:26.718Z</when>
+        <when>1970-01-01T00:00:28.139Z</when>
+        <when>1970-01-01T00:00:29.519Z</when>
+        <gx:coord>-32.061312 30.470605 26.03</gx:coord>
+        <gx:coord>-32.060351 30.471588</gx:coord>
+        <gx:coord>-32.059442 30.471933 30.62</gx:coord>
+        <gx:coord>-32.058752 30.472451 75.70</gx:coord>
+        <gx:coord>-32.058143 30.473278</gx:coord>
+        <gx:coord>-32.057275 30.474104 26.70</gx:coord>
+        <gx:coord>-32.056245 30.474413 72.49</gx:coord>
+        <gx:coord>-32.056020 30.475032</gx:coord>
+        <gx:coord>-32.055280 30.475289 57.64</gx:coord>
+        <gx:coord>-32.054461 30.475367 91.16</gx:coord>
+        <gx:coord>-32.054306 30.475838</gx:coord>
+        <gx:coord>-32.053593 30.476512 77.01</gx:coord>
+        <gx:coord>-32.052896 30.476701 2.66</gx:coord>
+        <gx:coord>-32.052293 30.477107 59.30</gx:coord>
+        <gx:coord>-32.051494 30.477113</gx:coord>
+        <gx:coord>-32.051415 30.477953 30.00</gx:coord>
+        <gx:coord>-32.051141 30.478722</gx:coord>
+        <ExtendedData>
+          <SchemaData schemaUrl="#schema">
+            <gx:SimpleArrayData name="cadence">
+              <gx:value>245</gx:value>
+              <gx:value>19</gx:value>
+              <gx:value>227</gx:value>
+              <gx:value>97</gx:value>
+              <gx:value>112</gx:value>
+              <gx:value>142</gx:value>
+              <gx:value>222</gx:value>
+              <gx:value>130</gx:value>
+              <gx:value>142</gx:value>
+              <gx:value>118</gx:value>
+              <gx:value>64</gx:value>
+              <gx:value>0</gx:value>
+              <gx:value>186</gx:value>
+              <gx:value>155</gx:value>
+              <gx:value>210</gx:value>
+              <gx:value>0</gx:value>
+              <gx:value>0</gx:value>
+            </gx:SimpleArrayData>
+            <gx:SimpleArrayData name="depth">
+              <gx:value>0.0</gx:value>
+              <gx:value>0.0</gx:value>
+              <gx:value>162.6</gx:value>
+              <gx:value>0.0</gx:value>
+              <gx:value>170.7</gx:value>
+              <gx:value>0.0</gx:value>
+              <gx:value>421.8</gx:value>
+              <gx:value>830.1</gx:value>
+              <gx:value>218.1</gx:value>
+              <gx:value>292.2</gx:value>
+              <gx:value>426.2</gx:value>
+              <gx:value>0.0</gx:value>
+              <gx:value>911.3</gx:value>
+              <gx:value>174.5</gx:value>
+              <gx:value>804.5</gx:value>
+              <gx:value>52.5</gx:value>
+              <gx:value>640.0</gx:value>
+            </gx:SimpleArrayData>
+            <gx:SimpleArrayData name="heartrate">
+              <gx:value>25</gx:value>
+              <gx:value>0</gx:value>
+              <gx:value>41</gx:value>
+              <gx:value>244</gx:value>
+              <gx:value>250</gx:value>
+              <gx:value>253</gx:value>
+              <gx:value>33</gx:value>
+              <gx:value>194</gx:value>
+              <gx:value>238</gx:value>
+              <gx:value>0</gx:value>
+              <gx:value>227</gx:value>
+              <gx:value>164</gx:value>
+              <gx:value>246</gx:value>
+              <gx:value>52</gx:value>
+              <gx:value>90</gx:value>
+              <gx:value>53</gx:value>
+              <gx:value>96</gx:value>
+            </gx:SimpleArrayData>
+            <gx:SimpleArrayData name="temperature">
+              <gx:value>0.0</gx:value>
+              <gx:value>17.8</gx:value>
+              <gx:value>4.4</gx:value>
+              <gx:value>12.7</gx:value>
+              <gx:value>0.0</gx:value>
+              <gx:value>0.0</gx:value>
+              <gx:value>2.2</gx:value>
+              <gx:value>7.9</gx:value>
+              <gx:value>4.7</gx:value>
+              <gx:value>31.9</gx:value>
+              <gx:value>0.0</gx:value>
+              <gx:value>24.3</gx:value>
+              <gx:value>23.2</gx:value>
+              <gx:value>28.0</gx:value>
+              <gx:value>21.8</gx:value>
+              <gx:value>0.0</gx:value>
+              <gx:value>0.0</gx:value>
+            </gx:SimpleArrayData>
+          </SchemaData>
+        </ExtendedData>
+      </gx:Track>
+    </Placemark>
+    <Folder>
+      <name>Track</name>
+      <snippet/>
+      <description>
+<![CDATA[<table>
+<tr><td><b>Distance</b> 4761.1 ft </td></tr>
+<tr><td><b>Min Alt</b> 8.740 ft </td></tr>
+<tr><td><b>Max Alt</b> 299.085 ft </td></tr>
+<tr><td><b>Min Speed</b> 57.9 mph </td></tr>
+<tr><td><b>Max Speed</b> 156.4 mph </td></tr>
+<tr><td><b>Avg Speed</b> 124.6 mph </td></tr>
+<tr><td><b>Avg Heart Rate</b> 147.1 bpm </td></tr>
+<tr><td><b>Min Heart Rate</b> 25 bpm </td></tr>
+<tr><td><b>Max Heart Rate</b> 253 bpm </td></tr>
+<tr><td><b>Avg Cadence</b> 147.8 rpm </td></tr>
+<tr><td><b>Max Cadence</b> 245 rpm </td></tr>
+<tr><td><b>Start Time</b>1970-01-01T00:00:03.464Z</td></tr>
+<tr><td><b>End Time</b>1970-01-01T00:00:29.519Z</td></tr>
+</table>]]>
+</description>
+      <TimeSpan>
+        <begin>1970-01-01T00:00:03.464Z</begin>
+        <end>1970-01-01T00:00:29.519Z</end>
+      </TimeSpan>
+      <Folder>
+        <name>Points</name>
+        <Placemark>
+          <name>Wpt_QB</name>
+          <snippet/>
+          <description><![CDATA[
+<table>
+<tr><td>Longitude: -32.061312 </td></tr>
+<tr><td>Latitude: 30.470605 </td></tr>
+<tr><td>Altitude: 85.400 ft </td></tr>
+<tr><td>Heart rate: 25 </td></tr>
+<tr><td>Cadence: 245 </td></tr>
+<tr><td>Heading: 317.9 </td></tr>
+</table>
+]]></description>
+          <LookAt>
+            <longitude>-32.061312</longitude>
+            <latitude>30.470605</latitude>
+            <tilt>66</tilt>
+          </LookAt>
+          <styleUrl>#track</styleUrl>
+          <Point>
+            <coordinates>-32.061312,30.470605,26.03</coordinates>
+          </Point>
+        </Placemark>
+        <Placemark>
+          <name>Wpt_Y8GPuG</name>
+          <snippet/>
+          <description><![CDATA[
+<table>
+<tr><td>Longitude: -32.060351 </td></tr>
+<tr><td>Latitude: 30.471588 </td></tr>
+<tr><td>Cadence: 19 </td></tr>
+<tr><td>Temperature: 17.8 </td></tr>
+<tr><td>Speed: 146.2 mph </td></tr>
+<tr><td>Heading: 40.1 </td></tr>
+<tr><td>Time: 1970-01-01T00:00:03.464Z </td></tr>
+</table>
+]]></description>
+          <LookAt>
+            <longitude>-32.060351</longitude>
+            <latitude>30.471588</latitude>
+            <tilt>66</tilt>
+          </LookAt>
+          <TimeStamp>
+            <when>1970-01-01T00:00:03.464Z</when>
+          </TimeStamp>
+          <styleUrl>#track</styleUrl>
+          <Point>
+            <coordinates>-32.060351,30.471588</coordinates>
+          </Point>
+        </Placemark>
+        <Placemark>
+          <name>Wpt_hTiV</name>
+          <snippet/>
+          <description><![CDATA[
+<table>
+<tr><td>Longitude: -32.059442 </td></tr>
+<tr><td>Latitude: 30.471933 </td></tr>
+<tr><td>Altitude: 100.458 ft </td></tr>
+<tr><td>Heart rate: 41 </td></tr>
+<tr><td>Cadence: 227 </td></tr>
+<tr><td>Temperature: 4.4 </td></tr>
+<tr><td>Depth: 533.4 ft </td></tr>
+<tr><td>Speed: 155.7 mph </td></tr>
+<tr><td>Heading: 66.3 </td></tr>
+<tr><td>Time: 1970-01-01T00:00:04.834Z </td></tr>
+</table>
+]]></description>
+          <LookAt>
+            <longitude>-32.059442</longitude>
+            <latitude>30.471933</latitude>
+            <tilt>66</tilt>
+          </LookAt>
+          <TimeStamp>
+            <when>1970-01-01T00:00:04.834Z</when>
+          </TimeStamp>
+          <styleUrl>#track</styleUrl>
+          <Point>
+            <coordinates>-32.059442,30.471933,30.62</coordinates>
+          </Point>
+        </Placemark>
+        <Placemark>
+          <name>Wpt_Fr0VTZa</name>
+          <snippet/>
+          <description><![CDATA[
+<table>
+<tr><td>Longitude: -32.058752 </td></tr>
+<tr><td>Latitude: 30.472451 </td></tr>
+<tr><td>Altitude: 248.355 ft </td></tr>
+<tr><td>Heart rate: 244 </td></tr>
+<tr><td>Cadence: 97 </td></tr>
+<tr><td>Temperature: 12.7 </td></tr>
+<tr><td>Speed: 152.0 mph </td></tr>
+<tr><td>Heading: 48.9 </td></tr>
+<tr><td>Time: 1970-01-01T00:00:06.126Z </td></tr>
+</table>
+]]></description>
+          <LookAt>
+            <longitude>-32.058752</longitude>
+            <latitude>30.472451</latitude>
+            <tilt>66</tilt>
+          </LookAt>
+          <TimeStamp>
+            <when>1970-01-01T00:00:06.126Z</when>
+          </TimeStamp>
+          <styleUrl>#track</styleUrl>
+          <Point>
+            <coordinates>-32.058752,30.472451,75.70</coordinates>
+          </Point>
+        </Placemark>
+        <Placemark>
+          <name>Wpt_8f</name>
+          <snippet/>
+          <description><![CDATA[
+<table>
+<tr><td>Longitude: -32.058143 </td></tr>
+<tr><td>Latitude: 30.473278 </td></tr>
+<tr><td>Heart rate: 250 </td></tr>
+<tr><td>Cadence: 112 </td></tr>
+<tr><td>Depth: 560.0 ft </td></tr>
+<tr><td>Speed: 156.4 mph </td></tr>
+<tr><td>Heading: 32.4 </td></tr>
+<tr><td>Time: 1970-01-01T00:00:07.686Z </td></tr>
+</table>
+]]></description>
+          <LookAt>
+            <longitude>-32.058143</longitude>
+            <latitude>30.473278</latitude>
+            <tilt>66</tilt>
+          </LookAt>
+          <TimeStamp>
+            <when>1970-01-01T00:00:07.686Z</when>
+          </TimeStamp>
+          <styleUrl>#track</styleUrl>
+          <Point>
+            <coordinates>-32.058143,30.473278</coordinates>
+          </Point>
+        </Placemark>
+        <Placemark>
+          <name>Wpt_R</name>
+          <snippet/>
+          <description><![CDATA[
+<table>
+<tr><td>Longitude: -32.057275 </td></tr>
+<tr><td>Latitude: 30.474104 </td></tr>
+<tr><td>Altitude: 87.606 ft </td></tr>
+<tr><td>Heart rate: 253 </td></tr>
+<tr><td>Cadence: 142 </td></tr>
+<tr><td>Speed: 153.6 mph </td></tr>
+<tr><td>Heading: 42.2 </td></tr>
+<tr><td>Time: 1970-01-01T00:00:09.493Z </td></tr>
+</table>
+]]></description>
+          <LookAt>
+            <longitude>-32.057275</longitude>
+            <latitude>30.474104</latitude>
+            <tilt>66</tilt>
+          </LookAt>
+          <TimeStamp>
+            <when>1970-01-01T00:00:09.493Z</when>
+          </TimeStamp>
+          <styleUrl>#track</styleUrl>
+          <Point>
+            <coordinates>-32.057275,30.474104,26.70</coordinates>
+          </Point>
+        </Placemark>
+        <Placemark>
+          <name>ESTIMATED Position</name>
+          <snippet/>
+          <description><![CDATA[
+<table>
+<tr><td>Longitude: -32.056245 </td></tr>
+<tr><td>Latitude: 30.474413 </td></tr>
+<tr><td>Altitude: 237.843 ft </td></tr>
+<tr><td>Heart rate: 33 </td></tr>
+<tr><td>Cadence: 222 </td></tr>
+<tr><td>Temperature: 2.2 </td></tr>
+<tr><td>Depth: 1383.8 ft </td></tr>
+<tr><td>Speed: 83.3 mph </td></tr>
+<tr><td>Heading: 70.8 </td></tr>
+<tr><td>Time: 1970-01-01T00:00:12.434Z </td></tr>
+</table>
+]]></description>
+          <LookAt>
+            <longitude>-32.056245</longitude>
+            <latitude>30.474413</latitude>
+            <tilt>66</tilt>
+          </LookAt>
+          <TimeStamp>
+            <when>1970-01-01T00:00:12.434Z</when>
+          </TimeStamp>
+          <styleUrl>#track</styleUrl>
+          <Point>
+            <coordinates>-32.056245,30.474413,72.49</coordinates>
+          </Point>
+        </Placemark>
+        <Placemark>
+          <name>Wpt_8JwVPYQ</name>
+          <snippet/>
+          <description><![CDATA[
+<table>
+<tr><td>Longitude: -32.056020 </td></tr>
+<tr><td>Latitude: 30.475032 </td></tr>
+<tr><td>Heart rate: 194 </td></tr>
+<tr><td>Cadence: 130 </td></tr>
+<tr><td>Temperature: 7.9 </td></tr>
+<tr><td>Depth: 2723.3 ft </td></tr>
+<tr><td>Speed: 91.8 mph </td></tr>
+<tr><td>Heading: 17.4 </td></tr>
+<tr><td>Time: 1970-01-01T00:00:14.191Z </td></tr>
+</table>
+]]></description>
+          <LookAt>
+            <longitude>-32.056020</longitude>
+            <latitude>30.475032</latitude>
+            <tilt>66</tilt>
+          </LookAt>
+          <TimeStamp>
+            <when>1970-01-01T00:00:14.191Z</when>
+          </TimeStamp>
+          <styleUrl>#track</styleUrl>
+          <Point>
+            <coordinates>-32.056020,30.475032</coordinates>
+          </Point>
+        </Placemark>
+        <Placemark>
+          <name>Wpt_e</name>
+          <snippet/>
+          <description><![CDATA[
+<table>
+<tr><td>Longitude: -32.055280 </td></tr>
+<tr><td>Latitude: 30.475289 </td></tr>
+<tr><td>Altitude: 189.100 ft </td></tr>
+<tr><td>Heart rate: 238 </td></tr>
+<tr><td>Cadence: 142 </td></tr>
+<tr><td>Temperature: 4.7 </td></tr>
+<tr><td>Depth: 715.4 ft </td></tr>
+<tr><td>Speed: 57.9 mph </td></tr>
+<tr><td>Heading: 68.0 </td></tr>
+<tr><td>Time: 1970-01-01T00:00:16.984Z </td></tr>
+</table>
+]]></description>
+          <LookAt>
+            <longitude>-32.055280</longitude>
+            <latitude>30.475289</latitude>
+            <tilt>66</tilt>
+          </LookAt>
+          <TimeStamp>
+            <when>1970-01-01T00:00:16.984Z</when>
+          </TimeStamp>
+          <styleUrl>#track</styleUrl>
+          <Point>
+            <coordinates>-32.055280,30.475289,57.64</coordinates>
+          </Point>
+        </Placemark>
+        <Placemark>
+          <name>ESTIMATED Position</name>
+          <snippet/>
+          <description><![CDATA[
+<table>
+<tr><td>Longitude: -32.054461 </td></tr>
+<tr><td>Latitude: 30.475367 </td></tr>
+<tr><td>Altitude: 299.085 ft </td></tr>
+<tr><td>Cadence: 118 </td></tr>
+<tr><td>Temperature: 31.9 </td></tr>
+<tr><td>Depth: 958.6 ft </td></tr>
+<tr><td>Speed: 91.8 mph </td></tr>
+<tr><td>Heading: 83.7 </td></tr>
+<tr><td>Time: 1970-01-01T00:00:18.913Z </td></tr>
+</table>
+]]></description>
+          <LookAt>
+            <longitude>-32.054461</longitude>
+            <latitude>30.475367</latitude>
+            <tilt>66</tilt>
+          </LookAt>
+          <TimeStamp>
+            <when>1970-01-01T00:00:18.913Z</when>
+          </TimeStamp>
+          <styleUrl>#track</styleUrl>
+          <Point>
+            <coordinates>-32.054461,30.475367,91.16</coordinates>
+          </Point>
+        </Placemark>
+        <Placemark>
+          <name>ESTIMATED Position</name>
+          <snippet/>
+          <description><![CDATA[
+<table>
+<tr><td>Longitude: -32.054306 </td></tr>
+<tr><td>Latitude: 30.475838 </td></tr>
+<tr><td>Heart rate: 227 </td></tr>
+<tr><td>Cadence: 64 </td></tr>
+<tr><td>Depth: 1398.4 ft </td></tr>
+<tr><td>Speed: 71.0 mph </td></tr>
+<tr><td>Heading: 15.8 </td></tr>
+<tr><td>Time: 1970-01-01T00:00:20.628Z </td></tr>
+</table>
+]]></description>
+          <LookAt>
+            <longitude>-32.054306</longitude>
+            <latitude>30.475838</latitude>
+            <tilt>66</tilt>
+          </LookAt>
+          <TimeStamp>
+            <when>1970-01-01T00:00:20.628Z</when>
+          </TimeStamp>
+          <styleUrl>#track</styleUrl>
+          <Point>
+            <coordinates>-32.054306,30.475838</coordinates>
+          </Point>
+        </Placemark>
+        <Placemark>
+          <name>Wpt_4</name>
+          <snippet/>
+          <description><![CDATA[
+<table>
+<tr><td>Longitude: -32.053593 </td></tr>
+<tr><td>Latitude: 30.476512 </td></tr>
+<tr><td>Altitude: 252.661 ft </td></tr>
+<tr><td>Heart rate: 164 </td></tr>
+<tr><td>Temperature: 24.3 </td></tr>
+<tr><td>Speed: 115.5 mph </td></tr>
+<tr><td>Heading: 42.4 </td></tr>
+<tr><td>Time: 1970-01-01T00:00:22.594Z </td></tr>
+</table>
+]]></description>
+          <LookAt>
+            <longitude>-32.053593</longitude>
+            <latitude>30.476512</latitude>
+            <tilt>66</tilt>
+          </LookAt>
+          <TimeStamp>
+            <when>1970-01-01T00:00:22.594Z</when>
+          </TimeStamp>
+          <styleUrl>#track</styleUrl>
+          <Point>
+            <coordinates>-32.053593,30.476512,77.01</coordinates>
+          </Point>
+        </Placemark>
+        <Placemark>
+          <name>Wpt_25dSc2Mb</name>
+          <snippet/>
+          <description><![CDATA[
+<table>
+<tr><td>Longitude: -32.052896 </td></tr>
+<tr><td>Latitude: 30.476701 </td></tr>
+<tr><td>Altitude: 8.740 ft </td></tr>
+<tr><td>Heart rate: 246 </td></tr>
+<tr><td>Cadence: 186 </td></tr>
+<tr><td>Temperature: 23.2 </td></tr>
+<tr><td>Depth: 2989.8 ft </td></tr>
+<tr><td>Speed: 114.1 mph </td></tr>
+<tr><td>Heading: 72.5 </td></tr>
+<tr><td>Time: 1970-01-01T00:00:23.968Z </td></tr>
+</table>
+]]></description>
+          <LookAt>
+            <longitude>-32.052896</longitude>
+            <latitude>30.476701</latitude>
+            <tilt>66</tilt>
+          </LookAt>
+          <TimeStamp>
+            <when>1970-01-01T00:00:23.968Z</when>
+          </TimeStamp>
+          <styleUrl>#track</styleUrl>
+          <Point>
+            <coordinates>-32.052896,30.476701,2.66</coordinates>
+          </Point>
+        </Placemark>
+        <Placemark>
+          <name>Wpt_iHp8</name>
+          <snippet/>
+          <description><![CDATA[
+<table>
+<tr><td>Longitude: -32.052293 </td></tr>
+<tr><td>Latitude: 30.477107 </td></tr>
+<tr><td>Altitude: 194.545 ft </td></tr>
+<tr><td>Heart rate: 52 </td></tr>
+<tr><td>Cadence: 155 </td></tr>
+<tr><td>Temperature: 28.0 </td></tr>
+<tr><td>Depth: 572.3 ft </td></tr>
+<tr><td>Speed: 140.7 mph </td></tr>
+<tr><td>Heading: 52.0 </td></tr>
+<tr><td>Time: 1970-01-01T00:00:25.135Z </td></tr>
+</table>
+]]></description>
+          <LookAt>
+            <longitude>-32.052293</longitude>
+            <latitude>30.477107</latitude>
+            <tilt>66</tilt>
+          </LookAt>
+          <TimeStamp>
+            <when>1970-01-01T00:00:25.135Z</when>
+          </TimeStamp>
+          <styleUrl>#track</styleUrl>
+          <Point>
+            <coordinates>-32.052293,30.477107,59.30</coordinates>
+          </Point>
+        </Placemark>
+        <Placemark>
+          <name>Wpt_lFMiR</name>
+          <snippet/>
+          <description><![CDATA[
+<table>
+<tr><td>Longitude: -32.051494 </td></tr>
+<tr><td>Latitude: 30.477113 </td></tr>
+<tr><td>Heart rate: 90 </td></tr>
+<tr><td>Cadence: 210 </td></tr>
+<tr><td>Temperature: 21.8 </td></tr>
+<tr><td>Depth: 2639.6 ft </td></tr>
+<tr><td>Speed: 108.3 mph </td></tr>
+<tr><td>Heading: 89.5 </td></tr>
+<tr><td>Time: 1970-01-01T00:00:26.718Z </td></tr>
+</table>
+]]></description>
+          <LookAt>
+            <longitude>-32.051494</longitude>
+            <latitude>30.477113</latitude>
+            <tilt>66</tilt>
+          </LookAt>
+          <TimeStamp>
+            <when>1970-01-01T00:00:26.718Z</when>
+          </TimeStamp>
+          <styleUrl>#track</styleUrl>
+          <Point>
+            <coordinates>-32.051494,30.477113</coordinates>
+          </Point>
+        </Placemark>
+        <Placemark>
+          <name>Wpt_k2lz15k</name>
+          <snippet/>
+          <description><![CDATA[
+<table>
+<tr><td>Longitude: -32.051415 </td></tr>
+<tr><td>Latitude: 30.477953 </td></tr>
+<tr><td>Altitude: 98.438 ft </td></tr>
+<tr><td>Heart rate: 53 </td></tr>
+<tr><td>Depth: 172.3 ft </td></tr>
+<tr><td>Speed: 147.6 mph </td></tr>
+<tr><td>Heading: 4.6 </td></tr>
+<tr><td>Time: 1970-01-01T00:00:28.139Z </td></tr>
+</table>
+]]></description>
+          <LookAt>
+            <longitude>-32.051415</longitude>
+            <latitude>30.477953</latitude>
+            <tilt>66</tilt>
+          </LookAt>
+          <TimeStamp>
+            <when>1970-01-01T00:00:28.139Z</when>
+          </TimeStamp>
+          <styleUrl>#track</styleUrl>
+          <Point>
+            <coordinates>-32.051415,30.477953,30.00</coordinates>
+          </Point>
+        </Placemark>
+        <Placemark>
+          <name>Wpt_hCT</name>
+          <snippet/>
+          <description><![CDATA[
+<table>
+<tr><td>Longitude: -32.051141 </td></tr>
+<tr><td>Latitude: 30.478722 </td></tr>
+<tr><td>Heart rate: 96 </td></tr>
+<tr><td>Depth: 2099.9 ft </td></tr>
+<tr><td>Speed: 145.1 mph </td></tr>
+<tr><td>Heading: 17.1 </td></tr>
+<tr><td>Time: 1970-01-01T00:00:29.519Z </td></tr>
+</table>
+]]></description>
+          <LookAt>
+            <longitude>-32.051141</longitude>
+            <latitude>30.478722</latitude>
+            <tilt>66</tilt>
+          </LookAt>
+          <TimeStamp>
+            <when>1970-01-01T00:00:29.519Z</when>
+          </TimeStamp>
+          <styleUrl>#track</styleUrl>
+          <Point>
+            <coordinates>-32.051141,30.478722</coordinates>
+          </Point>
+        </Placemark>
+      </Folder>
+      <Placemark>
+        <name>Path</name>
+        <styleUrl>#lineStyle</styleUrl>
+        <LineString>
+          <tessellate>1</tessellate>
+          <coordinates>
+-32.061312,30.470605,26.03
+-32.060351,30.471588
+-32.059442,30.471933,30.62
+-32.058752,30.472451,75.70
+-32.058143,30.473278
+-32.057275,30.474104,26.70
+-32.056245,30.474413,72.49
+-32.056020,30.475032
+-32.055280,30.475289,57.64
+-32.054461,30.475367,91.16
+-32.054306,30.475838
+-32.053593,30.476512,77.01
+-32.052896,30.476701,2.66
+-32.052293,30.477107,59.30
+-32.051494,30.477113
+-32.051415,30.477953,30.00
+-32.051141,30.478722
+</coordinates>
+        </LineString>
+      </Placemark>
+    </Folder>
+  </Document>
+</kml>
index 5a1d2fcfc91acc563d0c18ac09fa42a48fbb67c1..2863b86bdc321ba909e0f2d2b4fa4cbcdc85b8b4 100644 (file)
@@ -65,6 +65,10 @@ compare ${REFERENCE}/track/tracks~gpx.kml ${TMPDIR}/tracks~gpx.kml
 gpsbabel -i kml -f ${REFERENCE}/track/Placemark-Track-1.kml -o gpx -F ${TMPDIR}/Placemark-Track-1~kml.gpx
 compare ${REFERENCE}/track/Placemark-Track-1~kml.gpx ${TMPDIR}/Placemark-Track-1~kml.gpx
 
+# kml realtime writer
+gpsbabel -T -i random,points=20,seed=33,nodelay -f dummy -o kml,track -F  ${TMPDIR}/realtime.kml
+compare ${REFERENCE}/realtime.kml ${TMPDIR}/realtime.kml
+
 if [ "${RUNNINGVALGRIND}" != "0" ]; then
   set -e
   if which xmllint > /dev/null;
diff --git a/vecs.h b/vecs.h
index e482c4c2f0de28dc82a56c11561b9d88353e4a4b..403e4d052f1d3b6c2594ea5b6a6f378c3395bcb6 100644 (file)
--- a/vecs.h
+++ b/vecs.h
 #include "ggv_bin.h"
 #include "globalsat_sport.h"
 #include "gpx.h"
+#include "kml.h"
 #include "legacyformat.h"
 #include "lowranceusr.h"
 #include "mynav.h"
-#include "qstarz_bl_1000.h"
 #include "nmea.h"
 #include "qstarz_bl_1000.h"
 #include "random.h"
@@ -58,7 +58,6 @@ extern ff_vecs_t mapsend_vecs;
 extern ff_vecs_t mps_vecs;
 extern ff_vecs_t ozi_vecs;
 extern ff_vecs_t pcx_vecs;
-extern ff_vecs_t kml_vecs;
 #if MAXIMAL_ENABLED
 extern ff_vecs_t gpsutil_vecs;
 extern ff_vecs_t holux_vecs;
@@ -281,7 +280,7 @@ private:
   NmeaFormat nmea_fmt;
   LegacyFormat ozi_fmt {ozi_vecs};
   LegacyFormat pcx_fmt {pcx_vecs};
-  LegacyFormat kml_fmt {kml_vecs};
+  KmlFormat kml_fmt;
 #if MAXIMAL_ENABLED
   LegacyFormat gpsutil_fmt {gpsutil_vecs};
   LowranceusrFormat lowranceusr_fmt;